Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Patti U-aksorn
Published © GPL3+

M5Stack Calculator Face as ThaiQR Code EDC Payment Device

Utilizing an M5stack Faces Calculator as a QR code EDC payment device.

AdvancedShowcase (no instructions)1 hour2,065
M5Stack Calculator Face as ThaiQR Code EDC Payment Device

Things used in this project

Hardware components

M5 Faces Pocket Computer with Keyboard/PyGamer/Calculator
M5Stack M5 Faces Pocket Computer with Keyboard/PyGamer/Calculator
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Code

m5stack qr payment source code.ino

Arduino
/*
QR BsC Demo by using mpay qr unified api as payment engin
creator : Patti U-aksorn
*/

#include <M5Stack.h>
#include <WiFi.h>
#include <ESPmDNS.h>
#include <WiFiClient.h>
#include "WebServer.h"
#include <HTTPClient.h>
#include <Preferences.h>
//#include "FS.h"
//#include "SD.h"
//#include "SPI.h"


//menu val
char* page="main";
boolean paymentMode=false;
boolean haveConfig=false;
boolean wifiConnect=false;
String pin;
boolean firstRun=true;
byte selPI=0;
byte selHis=0;
unsigned long lastTime=0;
unsigned long totalOrderFile=0;
String orderHistory[6];

//keyboard
#define KEYBOARD_I2C_ADDR     0X08
#define KEYBOARD_INT          5

//setting val
const IPAddress apIP(192, 168, 0, 1);
const char* apSSID = "mPAY_SETUP";
boolean settingMode;
String ssidList;
String wifi_ssid;
String wifi_password;
String app_key;
WebServer webServer(80);  
Preferences preferences;

//web service val
const String baseURL = "http://your.api.gateway";
int httpStatus;
String amount="";
String orderID="";
String QRcode="";
String TXID="";

void setup() {
  // put your setup code here, to run once:
  m5.begin();
  Serial.begin(115200);
  Wire.begin();
  pinMode(KEYBOARD_INT, INPUT_PULLUP);
  M5.Lcd.fillScreen(WHITE);
}

void loop() {
  // put your main code here, to run repeatedly:
  M5.update();
  if (page=="main") {
    pageMain();
  }
  if (page=="config") {
    pageConfig();
  }
  if (page=="login") {
    pageLogin();
  }
  if (page=="formLogin") {
    formLogin();
  }  
  if (page=="selectPI") {
    pageSelectPI();
  }  
  if (page=="history") {
    viewHistory();
  }  
  if (page=="amount") {
    inputAmount();
  }
  if (page=="QR") {
    showQR();
  }
  if (page=="Success") {
    successPage();
  }
  if (page=="orderdetail") {
    orderDetail();
  }
}

void pageMain() {
  if (firstRun==true) {
    //load logo
    M5.Lcd.drawJpgFile(SD, "/image/logo.jpg");
    //header
    M5.Lcd.setCursor(2, 2);
    M5.Lcd.setTextColor(BLACK, WHITE);
    M5.Lcd.setTextSize(1);
    M5.Lcd.printf("mPAY Merchant Technical Team");  
    //create menu
    M5.Lcd.setCursor(20, 225);
    M5.Lcd.setTextColor(WHITE, BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.printf(".SETTING.");
    M5.Lcd.setTextColor(BLACK, GREEN);
    M5.Lcd.setCursor(215, 225);
    M5.Lcd.printf(".START.");
    firstRun=false;
  }
    
  //buttom event
  if (M5.BtnA.wasReleased()) {
    //goto config mode
    //Serial.println("button A");
    page="config";
    M5.Lcd.fillScreen(WHITE);
    preferences.begin("wifi-config");
    delay(10);
    if (restoreConfig()) {
      if (checkConnection()) {
        settingMode = false;
        startWebServer();
        return;
      }
    }
    settingMode = true;
    setupMode();
  } else if (M5.BtnC.wasReleased()) {
    //goto payment mode
    //M5.update();
    page="login";
    Serial.println("enter payment mode");
    M5.Lcd.fillScreen(WHITE);
    preferences.begin("wifi-config");
    delay(500);
    haveConfig=restoreConfig();
    firstRun=true;
    pageLogin();
  }  

}

void pageLogin() {
  if (haveConfig==true) {
    //if (wifiConnect==false) {
    if (WiFi.status() != WL_CONNECTED) {
      M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.setTextSize(2);
      M5.Lcd.setCursor(215, 225);
      M5.Lcd.printf(".RETRY.");      
      if (M5.BtnC.wasReleased()) {
        //re-try
        M5.Lcd.setTextColor(WHITE, BLACK);
        M5.Lcd.setTextSize(2);
        M5.Lcd.setCursor(0, 80);
        wifiConnect=checkConnection();
        if (wifiConnect==true) M5.Lcd.fillScreen(WHITE);
      }
    } else {
      //Serial.println("Wifi Connected.Please Login");
      //create menu
      M5.update();
      M5.Lcd.setTextColor(WHITE, BLACK);
      M5.Lcd.setTextSize(2);
      M5.Lcd.setCursor(30, 225);
      M5.Lcd.printf(".BACK.");      
      M5.Lcd.setCursor(132, 225);
      M5.Lcd.printf(".DEL.");      
      M5.Lcd.setTextColor(BLACK, GREEN);
      M5.Lcd.setCursor(215, 225);
      M5.Lcd.printf(".LOGIN.");      
      //goto login form
      //M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);  
      //formLogin();
      M5.Lcd.setTextColor(BLACK, WHITE);
      M5.Lcd.setTextSize(4);
      M5.Lcd.setCursor(10, 40);
      M5.Lcd.printf("Enter PIN.");      
      M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);
      //delay(500);
      page="formLogin";
      delay(500);
      //formLogin();
    }    
  } else {
    Serial.println("Not Config. Please go to setting");
    M5.Lcd.setCursor(5, 100);
    M5.Lcd.setTextColor(RED, WHITE);
    M5.Lcd.setTextSize(4);
    M5.Lcd.println("No Config");
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(5, 130);
    M5.Lcd.printf("Please go to Setting.");
  }
}

void formLogin() {
    //keyboard event
    if(digitalRead(KEYBOARD_INT) == LOW) {
      Wire.requestFrom(KEYBOARD_I2C_ADDR, 1);  // request 1 byte from keyboard
      while (Wire.available()) { 
        uint8_t key_val = Wire.read();                  // receive a byte as character
        if(key_val != 0) {
          Serial.print("getting byte : ");
          Serial.print(key_val);
          if(key_val >= 0x20 && key_val < 0x7F) { // ASCII String
            if (key_val=='=') {
              Serial.println("call web service login");
              wsLogin();
            } else {
              Serial.print(" KEY=");
              Serial.print((char)key_val);
              if (isDigit(char(key_val))==true) {   //key pad 0-9
                pin.concat(char(key_val));
                Serial.print("PIN.=");
                Serial.println(pin);
                M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);  
              }
            }
          }
        }
      }
    }
  
    //display pin on LCD
    M5.Lcd.setTextColor(WHITE, BLUE);
    M5.Lcd.setTextSize(4);
    int x = 160 - ((pin.length() * 20)/2);
    M5.Lcd.setCursor(x, 115);
    M5.Lcd.printf(pin.c_str());
                
    //button event
    if (M5.BtnA.wasReleased()) {
      //go back to main page
      M5.Lcd.fillScreen(WHITE);
      firstRun=true;
      pin="";
      page="main";
    } else if (M5.BtnB.wasReleased()) {   //del 1 char form pin
      if (pin.length()>0) {
        M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);  
        pin.remove(pin.length()-1,1);
      }
    } else if (M5.BtnC.wasReleased()) {   //connect ws to login
      if (WiFi.status() == WL_CONNECTED) {
        M5.Lcd.setCursor(5, 190);
        M5.Lcd.setTextColor(BLACK, WHITE);
        M5.Lcd.setTextSize(2);
        M5.Lcd.printf("Login....");        
        if (wsLogin()==true) {
          M5.Lcd.setCursor(5, 190);
          M5.Lcd.printf("PIN Corrected.    ");        
          delay(500);
          firstRun=true;
          m5.update();
          page="selectPI";
          M5.Lcd.fillScreen(WHITE);
          //pageSelectPI();
        } else {
          //M5.Lcd.setCursor(5, 190);
          if (httpStatus==200) {
            M5.Lcd.setCursor(5, 190);
            M5.Lcd.printf("Wrong PIN     ");        
          } else {
            M5.Lcd.setCursor(5, 190);
            M5.Lcd.printf("HTTP status = ");        
            M5.Lcd.print(httpStatus);        
          }
        }
      }
    }

}

void pageSelectPI() {
  if (WiFi.status() != WL_CONNECTED) {
    firstRun=false;
    page="main";
  }
  if (firstRun==true) {
    lastTime=0;
    //create header
    M5.Lcd.fillRect(0, 0, 320, 20, BLUE);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(WHITE, BLUE);
    M5.Lcd.setCursor(3, 3);
    M5.Lcd.printf("WIFI:OK");
    M5.Lcd.setCursor(120, 2);
    M5.Lcd.printf(wsGetDateTime().c_str());
    //create display
    M5.Lcd.fillRoundRect(40,30,270,40,10,BLUE);
    M5.Lcd.fillRoundRect(40,75,270,40,10,BLUE);
    M5.Lcd.fillRoundRect(40,120,270,40,10,BLUE);
    M5.Lcd.fillRoundRect(40,165,270,40,10,BLUE);
    M5.Lcd.setTextColor(WHITE, BLUE);
    M5.Lcd.setTextSize(3);
    M5.Lcd.setCursor(60, 40);
    M5.Lcd.printf("ThaiQR & VIA");      
    M5.Lcd.setCursor(60, 85);
    M5.Lcd.printf("LINE Pay");      
    M5.Lcd.setCursor(60, 130);
    M5.Lcd.setTextColor(WHITE, BLUE);
    M5.Lcd.printf("WeChat");      
    M5.Lcd.setTextColor(WHITE, BLUE);
    M5.Lcd.setCursor(60, 175);
    M5.Lcd.printf("view history");      
    //create menu
    M5.Lcd.setTextColor(WHITE, BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(15, 225);
    M5.Lcd.printf(".LOGOUT.");      
    M5.Lcd.setCursor(132, 225);
    M5.Lcd.setTextColor(WHITE, RED);
    M5.Lcd.printf(".SEL.");      
    M5.Lcd.setCursor(227, 225);    
    M5.Lcd.setTextColor(BLACK, GREEN);
    M5.Lcd.printf(".ENT.");      
    updateSelectPI();
    firstRun=false;
  }
  
    //button event
    if (M5.BtnA.wasReleased()) {    // logout to main page
      //go back to main page
      M5.Lcd.fillScreen(WHITE);
      firstRun=true;
      pin="";
      selPI=0;
      page="main";
    } else if (M5.BtnB.wasReleased()) {   // move selected PI
      selPI=selPI+1;
      if (selPI>3) selPI=0;
      updateSelectPI();
    } else if (M5.BtnC.wasReleased()) {   // enter
      if (selPI!=3)  {
        //goto input amount
        M5.Lcd.fillScreen(WHITE);
        firstRun=true;
        m5.update();
        page="amount";        
      } else {
        //goto history
        M5.Lcd.fillScreen(WHITE);
        firstRun=true;
        m5.update();
        page="history";        
      }
    }
}

void loadLast6order(fs::FS &fs, const char * dirname, uint8_t levels) {
    unsigned long i=0;
    String orderid;
    Serial.printf("Listing last 6 orders : %s\n", dirname);
    File root = fs.open(dirname);
    if(!root){
        Serial.println("Failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println("Not a directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if(levels){
                listDir(fs, file.name(), levels -1);
            }
        } else {
            i=i+1;
            if (totalOrderFile-i < 6) {
              orderid = file.name();
              orderid = orderid.substring(7,21);
              orderHistory[(i-totalOrderFile)*(-1)] = orderid;
              Serial.print("  ORDERID: ");
              Serial.print(orderid);              
              Serial.print("  FILE: ");
              Serial.print(file.name());
              Serial.print("  SIZE: ");
              Serial.println(file.size());
            }
        }
        file = root.openNextFile();
    }  
    for (int j=0; j<=5; j++) {
      Serial.print("  history index : ");
      Serial.print(j);
      Serial.print("  ORDERID: ");
      Serial.println(orderHistory[j]);                    
    }
}

void viewHistory() {
  if (firstRun==true) {
    lastTime=0;
    // list order file 
    //listDir(SD, "/", 0);
    listDir(SD, "/order", 0);
    loadLast6order(SD, "/order", 0);
    //create header
    M5.Lcd.fillRect(0, 0, 320, 20, BLUE);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(WHITE, BLUE);
    M5.Lcd.setCursor(3, 3);
    M5.Lcd.printf("View Order History");
    //create display history
    M5.Lcd.setTextSize(3);
    M5.Lcd.setTextColor(BLACK, WHITE);
    M5.Lcd.setCursor(45, 30);
    M5.Lcd.printf(orderHistory[0].c_str());
    M5.Lcd.setCursor(45, 60);
    M5.Lcd.printf(orderHistory[1].c_str());
    M5.Lcd.setCursor(45, 90);
    M5.Lcd.printf(orderHistory[2].c_str());
    M5.Lcd.setCursor(45, 120);
    M5.Lcd.printf(orderHistory[3].c_str());
    M5.Lcd.setCursor(45, 150);
    M5.Lcd.printf(orderHistory[4].c_str());
    M5.Lcd.setCursor(45, 180);
    M5.Lcd.printf(orderHistory[5].c_str());
    //create menu
    M5.Lcd.setTextColor(WHITE, BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(30, 225);
    M5.Lcd.printf(".BACK.");      
    M5.Lcd.setCursor(132, 225);
    M5.Lcd.setTextColor(WHITE, RED);
    M5.Lcd.printf(".SEL.");      
    M5.Lcd.setCursor(227, 225);    
    M5.Lcd.setTextColor(BLACK, GREEN);
    M5.Lcd.printf(".ENT.");      
    firstRun=false;
    updateSelectHistory();
  }

    //button event
    if (M5.BtnA.wasReleased()) {    // logout to main page
      //go back to select pi
      M5.Lcd.fillScreen(WHITE);
      firstRun=true;
      amount="";
      selHis=0;
      page="selectPI";
    } else if (M5.BtnB.wasReleased()) {   // move selected PI
      selHis = selHis + 1;
      if (selHis>5) selHis=0;
      updateSelectHistory();
    } else if (M5.BtnC.wasReleased()) {   // enter
      //goto order detail
      M5.Lcd.fillScreen(WHITE);
      firstRun=true;
      m5.update();
      page="orderdetail";        
  }
}

void updateSelectHistory() {
  M5.Lcd.fillRect(0, 21, 45, 200, WHITE);
  m5.update();
  M5.Lcd.setTextSize(3);
  if (selHis==0) {
    M5.Lcd.setCursor(5, 30);
    M5.Lcd.setTextColor(RED, WHITE);
    M5.Lcd.printf(">>");          
  }
  if (selHis==1) {
    M5.Lcd.setCursor(5, 60);
    M5.Lcd.setTextColor(RED, WHITE);
    M5.Lcd.printf(">>");          
  }
  if (selHis==2) {
    M5.Lcd.setCursor(5, 90);
    M5.Lcd.setTextColor(RED, WHITE);
    M5.Lcd.printf(">>");          
  }
  if (selHis==3) {
    M5.Lcd.setCursor(5, 120);
    M5.Lcd.setTextColor(RED, WHITE);
    M5.Lcd.printf(">>");          
  }  
  if (selHis==4) {
    M5.Lcd.setCursor(5, 150);
    M5.Lcd.setTextColor(RED, WHITE);
    M5.Lcd.printf(">>");          
  }  
  if (selHis==5) {
    M5.Lcd.setCursor(5, 180);
    M5.Lcd.setTextColor(RED, WHITE);
    M5.Lcd.printf(">>");          
  }    
}

void orderDetail() {
  if (firstRun==true) {
    lastTime=0;
    //load order detail
    //create header
    M5.Lcd.fillRect(0, 0, 320, 20, BLUE);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(WHITE, BLUE);
    M5.Lcd.setCursor(3, 3);
    M5.Lcd.print("Order ID : ");
    M5.Lcd.print(orderHistory[selHis].c_str());
    //create display history
    String fileName = "/order/" + orderHistory[selHis] + ".txt";
    String fileContent = readFile(SD,fileName.c_str());
    M5.Lcd.setTextSize(3);
    M5.Lcd.setTextColor(BLACK, WHITE);
    M5.Lcd.setCursor(20, 30);
    M5.Lcd.print(fileContent);
    //create menu
    M5.Lcd.setTextColor(WHITE, BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(30, 225);
    M5.Lcd.printf(".BACK.");      
    M5.Lcd.setCursor(115, 225);
    M5.Lcd.setTextColor(WHITE, RED);
    M5.Lcd.printf(".REFUND.");      
    M5.Lcd.setCursor(227, 225);    
    M5.Lcd.setTextColor(BLACK, GREEN);
    M5.Lcd.printf(".CHK.");      
    firstRun=false;
  }

    //button event
    if (M5.BtnA.wasReleased()) {    // logout to main page
      //go back to select pi
      M5.Lcd.fillScreen(WHITE);
      firstRun=true;
      page="history";
    } else if (M5.BtnB.wasReleased()) {   // move selected PI
      //selHis = selHis + 1;
      //if (selHis>5) selHis=0;
      //updateSelectHistory();
    } else if (M5.BtnC.wasReleased()) {   // enter
      //goto order detail
      M5.Lcd.fillScreen(WHITE);
      firstRun=true;
      m5.update();
      page="orderdetail";        
    }
  
}

String readFile(fs::FS &fs, const char * path){
    String detail;
    char ch;
    Serial.printf("Reading file: %s\n", path);
    File file = fs.open(path);
    if(!file){
        Serial.println("Failed to open file for reading");
        return "";
    }
    Serial.print("Read from file: ");
    while(file.available()){
        ch = file.read();
        detail = detail + ch;
        Serial.write(file.read());
        //Serial.write(detail.c_str());
        
    }
    Serial.println("Read from file: ");
    Serial.print(detail);
    return detail;
    file.close();
}

void inputAmount() {
  //m5.update();
  if (WiFi.status() != WL_CONNECTED) {
    firstRun=false;
    pin="";
    amount="";
    page="main";
  }
  if (firstRun==true) {
    lastTime=0;
    //create header
    M5.Lcd.fillRect(0, 0, 320, 20, BLUE);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(WHITE, BLUE);
    M5.Lcd.setCursor(3, 3);
    M5.Lcd.printf("WIFI:OK");
    M5.Lcd.setCursor(120, 2);
    M5.Lcd.printf(wsGetDateTime().c_str());
    //create display
    M5.Lcd.setTextColor(BLACK, WHITE);
    M5.Lcd.setTextSize(4);
    M5.Lcd.setCursor(10, 40);
    M5.Lcd.printf("Enter Amount");      
    M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);
    //create menu
    M5.Lcd.setTextColor(WHITE, BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(30, 225);
    M5.Lcd.printf(".BACK.");      
    M5.Lcd.setCursor(132, 225);
    M5.Lcd.printf(".DEL.");      
    M5.Lcd.setTextColor(BLACK, GREEN);
    M5.Lcd.setCursor(235, 225);
    M5.Lcd.printf(".OK.");      
    firstRun=false;
  }

  //update current timestamp
  /*
  lastTime = millis();
  if (lastTime > 60000) {
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(WHITE, BLUE);
    M5.Lcd.setCursor(120, 2);
    M5.Lcd.printf(wsGetDateTime().c_str());    
    lastTime=0;
  }
  */

    //keyboard event
    if(digitalRead(KEYBOARD_INT) == LOW) {
      Wire.requestFrom(KEYBOARD_I2C_ADDR, 1);  // request 1 byte from keyboard
      while (Wire.available()) { 
        uint8_t key_val = Wire.read();                  // receive a byte as character
        if(key_val != 0) {
          Serial.print("getting byte : ");
          Serial.print(key_val);
          if(key_val >= 0x20 && key_val < 0x7F) { // ASCII String
            if (key_val=='=') {
              Serial.println("call web service QR");
              //wsLogin();
            } else {
              Serial.print(" KEY=");
              Serial.print((char)key_val);
              if (isDigit(char(key_val))==true || key_val=='.') {   //key pad 0-9
                amount.concat(char(key_val));
                Serial.print("Amount=");
                Serial.println(amount);
                M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);  
              }
            }
          }
        }
      }
    }
  
    //display amount
    M5.Lcd.setTextColor(WHITE, BLUE);
    M5.Lcd.setTextSize(4);
    int x = 160 - ((amount.length() * 20)/2);
    M5.Lcd.setCursor(x, 115);
    M5.Lcd.printf(amount.c_str());

    //button event
    if (M5.BtnA.wasReleased()) {    //back
      //go back to main page
      M5.Lcd.fillScreen(WHITE);
      firstRun=true;
      amount="";
      page="selectPI";
    } else if (M5.BtnB.wasReleased()) {   //del 1 char form amount
      if (amount.length()>0) {
        M5.Lcd.fillRoundRect(10,90,300,80,20,BLUE);  
        amount.remove(amount.length()-1,1);
      } else {
        amount="";
      }
    } else if (M5.BtnC.wasReleased()) {   //create QR
      if (WiFi.status() == WL_CONNECTED) {
        M5.Lcd.setCursor(5, 190);
        M5.Lcd.setTextColor(BLACK, WHITE);
        M5.Lcd.setTextSize(2);
        M5.Lcd.printf("Checking amount...");        
        if (checkAmount()==true) {
          M5.Lcd.setCursor(5, 190);
          M5.Lcd.setTextColor(BLACK, WHITE);
          M5.Lcd.setTextSize(2);
          M5.Lcd.printf("Creating QR Code...");        
          orderID=wsOrderID();
          QRcode=wsQR();
          if (QRcode!="fail") {
            Serial.print("QR code = ");
            Serial.println(QRcode);            
            M5.Lcd.fillScreen(WHITE);
            firstRun=true;
            m5.update();
            page="QR";
          } else {
            M5.Lcd.setCursor(5, 190);
            M5.Lcd.setTextColor(RED, WHITE);
            M5.Lcd.setTextSize(2);
            M5.Lcd.printf("Create QR Code Fail.");                                
          }
        } else {
          M5.Lcd.setCursor(5, 190);
          M5.Lcd.setTextColor(BLACK, WHITE);
          M5.Lcd.setTextSize(2);
          M5.Lcd.printf("Amount not correct.");                    
        }
      }
    }
  
}

void showQR() {
  if (firstRun==true) {
    lastTime=0;
    //create QR code display
    M5.Lcd.qrcode(QRcode,50,10,220,10);
    //create header
    String pi="";
    if (selPI==0) pi="ThaiQR & VIA";
    if (selPI==1) pi="Rabbit LINE Pay";    
    if (selPI==2) pi="Wechat";    
    //M5.Lcd.fillRect(0, 0, 320, 10, BLUE);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(BLACK, WHITE);
    M5.Lcd.setCursor(3, 3);
    M5.Lcd.printf("QR:");
    M5.Lcd.setCursor(40, 3);
    M5.Lcd.print(pi);
    //create menu
    M5.Lcd.setTextColor(WHITE, BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(30, 225);
    M5.Lcd.printf(".BACK.");      
    M5.Lcd.setTextColor(BLACK, GREEN);
    M5.Lcd.setCursor(215, 225);
    M5.Lcd.printf(".QUERY.");      
    firstRun=false;
    for (int i = 1; i <= 10; i++) {   // loop auto check payment status for 1 min.
      //buttom event
      m5.update();
      if (M5.BtnA.wasReleased()) {    //back
        //go back to main page
        M5.Lcd.fillScreen(WHITE);
        firstRun=true;
        amount="";
        page="selectPI";
      } else if (M5.BtnC.wasReleased()) {   //query payment status
        if (chkPayment()==true) {
          page="Success";
        }        
      }  
      delay(3000);
      if (chkPayment()==true) {
        page="Success";
        firstRun=true;
        break;
      }
      M5.Lcd.setTextSize(2);
      M5.Lcd.setTextColor(BLACK, WHITE);
      M5.Lcd.setCursor(290, 3);
      M5.Lcd.print(i);
    }  
  }
    //buttom event
    if (M5.BtnA.wasReleased()) {    //back
      M5.Lcd.setTextSize(2);
      M5.Lcd.setTextColor(BLACK, WHITE);
      M5.Lcd.setCursor(3, 3);
      M5.Lcd.printf("Checking Payment...     ");
      String q=wsQuery();
      String fileOrder = "/order/"+orderID+".txt";
      writeFile(SD,fileOrder.c_str(),q.c_str());      
      //go back to main page
      M5.Lcd.fillScreen(WHITE);
      firstRun=true;
      amount="";
      page="selectPI";
    } else if (M5.BtnC.wasReleased()) {   //query payment status
      if (chkPayment()==true) {
        page="Success";
      }        
    }  
}

void successPage() {
  if (firstRun==true) {
    //create display
    M5.Lcd.fillRoundRect(20,20,280,185,20,GREEN);
    M5.Lcd.setTextSize(4);
    M5.Lcd.setTextColor(BLACK, GREEN);
    M5.Lcd.setCursor(50, 70);
    M5.Lcd.printf(" SUCCESS");          
    M5.Lcd.setCursor(50, 120);
    M5.Lcd.print(TXID);          
    //create menu
    M5.Lcd.setTextSize(2);
    M5.Lcd.setTextColor(BLACK, GREEN);
    M5.Lcd.setCursor(235, 225);
    M5.Lcd.printf(".OK.");      
    M5.Speaker.beep();
    firstRun=false;
  }
    if (M5.BtnC.wasReleased()) {   //back to select PI
      M5.Lcd.fillScreen(WHITE);
      firstRun=true;
      amount="";
      TXID="";
      orderID="";
      page="selectPI";
    }    
}

boolean chkPayment() {
      if (WiFi.status() == WL_CONNECTED) {
        M5.Lcd.setTextSize(2);
        M5.Lcd.setTextColor(BLACK, WHITE);
        M5.Lcd.setCursor(3, 3);
        M5.Lcd.printf("Checking Payment...     ");
        String q=wsQuery();
        if (q.substring(0,14)=="STATUS=SUCCESS") {
          Serial.println("Payment SUCCESS");
          //save payment status to sd card for history
          String fileOrder = "/order/"+orderID+".txt";
          writeFile(SD,fileOrder.c_str(),q.c_str());
          M5.Lcd.setTextSize(2);
          M5.Lcd.setTextColor(BLACK, WHITE);
          M5.Lcd.setCursor(3, 3);
          M5.Lcd.printf("Payment SUCCESS.     ");
          delay(1000);
          int f=q.indexOf("TXID=");
          TXID=q.substring(f+5);
          Serial.print("TXID = ");
          Serial.println(TXID);
          M5.Lcd.fillScreen(WHITE);
          firstRun=true;
          m5.update();
          //page="Success";          
          return true;
        } else {
          Serial.println("Payment PENDING");
          //M5.Lcd.fillRect(0, 0, 320, 10, BLUE);          
          M5.Lcd.setTextSize(2);
          M5.Lcd.setTextColor(BLACK, WHITE);
          M5.Lcd.setCursor(3, 3);
          M5.Lcd.printf("Payment PENDING...    ");
          return false;
        }
      }  
}

void updateSelectPI() {
  m5.update();
  M5.Lcd.setTextSize(3);
  if (selPI==0) {
    M5.Lcd.fillRoundRect(5,30,30,40,5,RED);
    M5.Lcd.setCursor(10, 40);
    M5.Lcd.setTextColor(WHITE, RED);
    M5.Lcd.printf(">");          
  } else {
    M5.Lcd.fillRoundRect(5,30,30,40,5,WHITE);
    M5.Lcd.setCursor(10, 40);
    M5.Lcd.setTextColor(WHITE, WHITE);
    M5.Lcd.printf(">");              
  }
  if (selPI==1) {
    M5.Lcd.fillRoundRect(5,75,30,40,5,RED);
    M5.Lcd.setCursor(10, 85);
    M5.Lcd.setTextColor(WHITE, RED);
    M5.Lcd.printf(">");          
  } else {
    M5.Lcd.fillRoundRect(5,75,30,40,5,WHITE);
    M5.Lcd.setCursor(10, 85);
    M5.Lcd.setTextColor(WHITE, WHITE);
    M5.Lcd.printf(">");              
  }
  if (selPI==2) {
    M5.Lcd.fillRoundRect(5,120,30,40,5,RED);
    M5.Lcd.setCursor(10, 130);
    M5.Lcd.setTextColor(WHITE, RED);
    M5.Lcd.printf(">");          
  } else {
    M5.Lcd.fillRoundRect(5,120,30,40,5,WHITE);
    M5.Lcd.setCursor(10, 130);
    M5.Lcd.setTextColor(WHITE, WHITE);
    M5.Lcd.printf(">");              
  }
  if (selPI==3) {
    M5.Lcd.fillRoundRect(5,165,30,40,5,RED);
    M5.Lcd.setCursor(10, 175);
    M5.Lcd.setTextColor(WHITE, RED);
    M5.Lcd.printf(">");          
  } else {
    M5.Lcd.fillRoundRect(5,165,30,40,5,WHITE);
    M5.Lcd.setCursor(10, 175);
    M5.Lcd.setTextColor(WHITE, WHITE);
    M5.Lcd.printf(">");              
  }  
}

void writeFile(fs::FS &fs, const char * path, const char * message) {
  Serial.printf("Writing file: %s\n", path);
  File file = fs.open(path, FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for writing");
    return;
  }
  if (file.print(message)) {
    Serial.println("File written");
  } else {
    Serial.println("Write failed");
  }
}

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
    unsigned long i=0;
    Serial.printf("Listing directory: %s\n", dirname);
    File root = fs.open(dirname);
    if(!root){
        Serial.println("Failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println("Not a directory");
        return;
    }
    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if(levels){
                listDir(fs, file.name(), levels -1);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("  SIZE: ");
            Serial.println(file.size());
            i=i+1;
        }
        file = root.openNextFile();
    }
    totalOrderFile=i;
    Serial.print("  Toltal Files : ");
    Serial.println(i);    
}

String wsQuery() {
  String url = baseURL + app_key + "/inquery.asp?orderid=" + orderID;
  Serial.print("URL = ");
  Serial.println(url.c_str());
  HTTPClient http;
  http.begin(url); //HTTP
  int httpCode = http.GET();
  httpStatus = httpCode;
  if(httpCode > 0) {
    // HTTP header has been send and Server response header has been handled
    //USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode);
    // file found at server
    Serial.print("HTTP status = ");
    Serial.println(httpCode);
    if(httpCode == HTTP_CODE_OK) {
      String payload = http.getString();
      Serial.print("WS return = ");
      Serial.println(payload);
      return payload;
    } else {
      return "fail";
    }
  } else {
    //Serial.print("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
    return "fail";
  }
  http.end();    
}

String wsOrderID() {
  String url = baseURL + app_key + "/getOrderID.asp";
  Serial.print("URL = ");
  Serial.println(url.c_str());
  HTTPClient http;
  http.begin(url); //HTTP
  int httpCode = http.GET();
  httpStatus = httpCode;
  if(httpCode > 0) {
    // HTTP header has been send and Server response header has been handled
    //USE_SERIAL.printf("[HTTP] GET... code: %d\n", httpCode);
    // file found at server
    Serial.print("HTTP status = ");
    Serial.println(httpCode);
    if(httpCode == HTTP_CODE_OK) {
      String payload = http.getString();
      Serial.print("OrderID = ");
      Serial.println(payload);
      return payload;
    }
  } else {
    return "--";
  }
...

This file has been truncated, please download it to see its full contents.

Parsing.cpp

Arduino
/*
  Parsing.cpp - HTTP request parsing.

  Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/

#include <Arduino.h>
#include "WiFiServer.h"
#include "WiFiClient.h"
#include "WebServer.h"

//#define DEBUG_ESP_HTTP_SERVER
#ifdef DEBUG_ESP_PORT
#define DEBUG_OUTPUT DEBUG_ESP_PORT
#else
#define DEBUG_OUTPUT Serial
#endif

static char* readBytesWithTimeout(WiFiClient& client, size_t maxLength, size_t& dataLength, int timeout_ms)
{
  char *buf = nullptr;
  dataLength = 0;
  while (dataLength < maxLength) {
    int tries = timeout_ms;
    size_t newLength;
    while (!(newLength = client.available()) && tries--) delay(1);
    if (!newLength) {
      break;
    }
    if (!buf) {
      buf = (char *) malloc(newLength + 1);
      if (!buf) {
        return nullptr;
      }
    }
    else {
      char* newBuf = (char *) realloc(buf, dataLength + newLength + 1);
      if (!newBuf) {
        free(buf);
        return nullptr;
      }
      buf = newBuf;
    }
    client.readBytes(buf + dataLength, newLength);
    dataLength += newLength;
    buf[dataLength] = '\0';
  }
  return buf;
}

bool WebServer::_parseRequest(WiFiClient& client) {
  // Read the first line of HTTP request
  String req = client.readStringUntil('\r');
  client.readStringUntil('\n');
  //reset header value
  for (int i = 0; i < _headerKeysCount; ++i) {
    _currentHeaders[i].value =String();
   }

  // First line of HTTP request looks like "GET /path HTTP/1.1"
  // Retrieve the "/path" part by finding the spaces
  int addr_start = req.indexOf(' ');
  int addr_end = req.indexOf(' ', addr_start + 1);
  if (addr_start == -1 || addr_end == -1) {
#ifdef DEBUG_ESP_HTTP_SERVER
    DEBUG_OUTPUT.print("Invalid request: ");
    DEBUG_OUTPUT.println(req);
#endif
    return false;
  }

  String methodStr = req.substring(0, addr_start);
  String url = req.substring(addr_start + 1, addr_end);
  String versionEnd = req.substring(addr_end + 8);
  _currentVersion = atoi(versionEnd.c_str());
  String searchStr = "";
  int hasSearch = url.indexOf('?');
  if (hasSearch != -1){
    searchStr = urlDecode(url.substring(hasSearch + 1));
    url = url.substring(0, hasSearch);
  }
  _currentUri = url;
  _chunked = false;

  HTTPMethod method = HTTP_GET;
  if (methodStr == "POST") {
    method = HTTP_POST;
  } else if (methodStr == "DELETE") {
    method = HTTP_DELETE;
  } else if (methodStr == "OPTIONS") {
    method = HTTP_OPTIONS;
  } else if (methodStr == "PUT") {
    method = HTTP_PUT;
  } else if (methodStr == "PATCH") {
    method = HTTP_PATCH;
  }
  _currentMethod = method;

#ifdef DEBUG_ESP_HTTP_SERVER
  DEBUG_OUTPUT.print("method: ");
  DEBUG_OUTPUT.print(methodStr);
  DEBUG_OUTPUT.print(" url: ");
  DEBUG_OUTPUT.print(url);
  DEBUG_OUTPUT.print(" search: ");
  DEBUG_OUTPUT.println(searchStr);
#endif

  //attach handler
  RequestHandler* handler;
  for (handler = _firstHandler; handler; handler = handler->next()) {
    if (handler->canHandle(_currentMethod, _currentUri))
      break;
  }
  _currentHandler = handler;

  String formData;
  // below is needed only when POST type request
  if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){
    String boundaryStr;
    String headerName;
    String headerValue;
    bool isForm = false;
    bool isEncoded = false;
    uint32_t contentLength = 0;
    //parse headers
    while(1){
      req = client.readStringUntil('\r');
      client.readStringUntil('\n');
      if (req == "") break;//no moar headers
      int headerDiv = req.indexOf(':');
      if (headerDiv == -1){
        break;
      }
      headerName = req.substring(0, headerDiv);
      headerValue = req.substring(headerDiv + 1);
      headerValue.trim();
       _collectHeader(headerName.c_str(),headerValue.c_str());

      #ifdef DEBUG_ESP_HTTP_SERVER
      DEBUG_OUTPUT.print("headerName: ");
      DEBUG_OUTPUT.println(headerName);
      DEBUG_OUTPUT.print("headerValue: ");
      DEBUG_OUTPUT.println(headerValue);
      #endif

      if (headerName.equalsIgnoreCase("Content-Type")){
        if (headerValue.startsWith("text/plain")){
          isForm = false;
        } else if (headerValue.startsWith("application/x-www-form-urlencoded")){
          isForm = false;
          isEncoded = true;
        } else if (headerValue.startsWith("multipart/")){
          boundaryStr = headerValue.substring(headerValue.indexOf('=')+1);
          isForm = true;
        }
      } else if (headerName.equalsIgnoreCase("Content-Length")){
        contentLength = headerValue.toInt();
      } else if (headerName.equalsIgnoreCase("Host")){
        _hostHeader = headerValue;
      }
    }

    if (!isForm){
      size_t plainLength;
      char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
      if (plainLength < contentLength) {
      	free(plainBuf);
      	return false;
      }
      if (contentLength > 0) {
        if (searchStr != "") searchStr += '&';
        if(isEncoded){
          //url encoded form
          String decoded = urlDecode(plainBuf);
          size_t decodedLen = decoded.length();
          memcpy(plainBuf, decoded.c_str(), decodedLen);
          plainBuf[decodedLen] = 0;
          searchStr += plainBuf;
        }
        _parseArguments(searchStr);
        if(!isEncoded){
          //plain post json or other data
          RequestArgument& arg = _currentArgs[_currentArgCount++];
          arg.key = "plain";
          arg.value = String(plainBuf);
        }

  #ifdef DEBUG_ESP_HTTP_SERVER
        DEBUG_OUTPUT.print("Plain: ");
        DEBUG_OUTPUT.println(plainBuf);
  #endif
        free(plainBuf);
      }
    }

    if (isForm){
      _parseArguments(searchStr);
      if (!_parseForm(client, boundaryStr, contentLength)) {
        return false;
      }
    }
  } else {
    String headerName;
    String headerValue;
    //parse headers
    while(1){
      req = client.readStringUntil('\r');
      client.readStringUntil('\n');
      if (req == "") break;//no moar headers
      int headerDiv = req.indexOf(':');
      if (headerDiv == -1){
        break;
      }
      headerName = req.substring(0, headerDiv);
      headerValue = req.substring(headerDiv + 2);
      _collectHeader(headerName.c_str(),headerValue.c_str());

	  #ifdef DEBUG_ESP_HTTP_SERVER
	  DEBUG_OUTPUT.print("headerName: ");
	  DEBUG_OUTPUT.println(headerName);
	  DEBUG_OUTPUT.print("headerValue: ");
	  DEBUG_OUTPUT.println(headerValue);
	  #endif

	  if (headerName.equalsIgnoreCase("Host")){
        _hostHeader = headerValue;
      }
    }
    _parseArguments(searchStr);
  }
  client.flush();

#ifdef DEBUG_ESP_HTTP_SERVER
  DEBUG_OUTPUT.print("Request: ");
  DEBUG_OUTPUT.println(url);
  DEBUG_OUTPUT.print(" Arguments: ");
  DEBUG_OUTPUT.println(searchStr);
#endif

  return true;
}

bool WebServer::_collectHeader(const char* headerName, const char* headerValue) {
  for (int i = 0; i < _headerKeysCount; i++) {
    if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
            _currentHeaders[i].value=headerValue;
            return true;
        }
  }
  return false;
}

void WebServer::_parseArguments(String data) {
#ifdef DEBUG_ESP_HTTP_SERVER
  DEBUG_OUTPUT.print("args: ");
  DEBUG_OUTPUT.println(data);
#endif
  if (_currentArgs)
    delete[] _currentArgs;
  _currentArgs = 0;
  if (data.length() == 0) {
    _currentArgCount = 0;
    _currentArgs = new RequestArgument[1];
    return;
  }
  _currentArgCount = 1;

  for (int i = 0; i < (int)data.length(); ) {
    i = data.indexOf('&', i);
    if (i == -1)
      break;
    ++i;
    ++_currentArgCount;
  }
#ifdef DEBUG_ESP_HTTP_SERVER
  DEBUG_OUTPUT.print("args count: ");
  DEBUG_OUTPUT.println(_currentArgCount);
#endif

  _currentArgs = new RequestArgument[_currentArgCount+1];
  int pos = 0;
  int iarg;
  for (iarg = 0; iarg < _currentArgCount;) {
    int equal_sign_index = data.indexOf('=', pos);
    int next_arg_index = data.indexOf('&', pos);
#ifdef DEBUG_ESP_HTTP_SERVER
    DEBUG_OUTPUT.print("pos ");
    DEBUG_OUTPUT.print(pos);
    DEBUG_OUTPUT.print("=@ ");
    DEBUG_OUTPUT.print(equal_sign_index);
    DEBUG_OUTPUT.print(" &@ ");
    DEBUG_OUTPUT.println(next_arg_index);
#endif
    if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
#ifdef DEBUG_ESP_HTTP_SERVER
      DEBUG_OUTPUT.print("arg missing value: ");
      DEBUG_OUTPUT.println(iarg);
#endif
      if (next_arg_index == -1)
        break;
      pos = next_arg_index + 1;
      continue;
    }
    RequestArgument& arg = _currentArgs[iarg];
    arg.key = data.substring(pos, equal_sign_index);
	arg.value = data.substring(equal_sign_index + 1, next_arg_index);
#ifdef DEBUG_ESP_HTTP_SERVER
    DEBUG_OUTPUT.print("arg ");
    DEBUG_OUTPUT.print(iarg);
    DEBUG_OUTPUT.print(" key: ");
    DEBUG_OUTPUT.print(arg.key);
    DEBUG_OUTPUT.print(" value: ");
    DEBUG_OUTPUT.println(arg.value);
#endif
    ++iarg;
    if (next_arg_index == -1)
      break;
    pos = next_arg_index + 1;
  }
  _currentArgCount = iarg;
#ifdef DEBUG_ESP_HTTP_SERVER
  DEBUG_OUTPUT.print("args count: ");
  DEBUG_OUTPUT.println(_currentArgCount);
#endif

}

void WebServer::_uploadWriteByte(uint8_t b){
  if (_currentUpload.currentSize == HTTP_UPLOAD_BUFLEN){
    if(_currentHandler && _currentHandler->canUpload(_currentUri))
      _currentHandler->upload(*this, _currentUri, _currentUpload);
    _currentUpload.totalSize += _currentUpload.currentSize;
    _currentUpload.currentSize = 0;
  }
  _currentUpload.buf[_currentUpload.currentSize++] = b;
}

uint8_t WebServer::_uploadReadByte(WiFiClient& client){
  int res = client.read();
  if(res == -1){
    while(!client.available() && client.connected())
      yield();
    res = client.read();
  }
  return (uint8_t)res;
}

bool WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){
  (void) len;
#ifdef DEBUG_ESP_HTTP_SERVER
  DEBUG_OUTPUT.print("Parse Form: Boundary: ");
  DEBUG_OUTPUT.print(boundary);
  DEBUG_OUTPUT.print(" Length: ");
  DEBUG_OUTPUT.println(len);
#endif
  String line;
  int retry = 0;
  do {
    line = client.readStringUntil('\r');
    ++retry;
  } while (line.length() == 0 && retry < 3);

  client.readStringUntil('\n');
  //start reading the form
  if (line == ("--"+boundary)){
    RequestArgument* postArgs = new RequestArgument[32];
    int postArgsLen = 0;
    while(1){
      String argName;
      String argValue;
      String argType;
      String argFilename;
      bool argIsFile = false;

      line = client.readStringUntil('\r');
      client.readStringUntil('\n');
      if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase("Content-Disposition")){
        int nameStart = line.indexOf('=');
        if (nameStart != -1){
          argName = line.substring(nameStart+2);
          nameStart = argName.indexOf('=');
          if (nameStart == -1){
            argName = argName.substring(0, argName.length() - 1);
          } else {
            argFilename = argName.substring(nameStart+2, argName.length() - 1);
            argName = argName.substring(0, argName.indexOf('"'));
            argIsFile = true;
#ifdef DEBUG_ESP_HTTP_SERVER
            DEBUG_OUTPUT.print("PostArg FileName: ");
            DEBUG_OUTPUT.println(argFilename);
#endif
            //use GET to set the filename if uploading using blob
            if (argFilename == "blob" && hasArg("filename")) argFilename = arg("filename");
          }
#ifdef DEBUG_ESP_HTTP_SERVER
          DEBUG_OUTPUT.print("PostArg Name: ");
          DEBUG_OUTPUT.println(argName);
#endif
          argType = "text/plain";
          line = client.readStringUntil('\r');
          client.readStringUntil('\n');
          if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase("Content-Type")){
            argType = line.substring(line.indexOf(':')+2);
            //skip next line
            client.readStringUntil('\r');
            client.readStringUntil('\n');
          }
#ifdef DEBUG_ESP_HTTP_SERVER
          DEBUG_OUTPUT.print("PostArg Type: ");
          DEBUG_OUTPUT.println(argType);
#endif
          if (!argIsFile){
            while(1){
              line = client.readStringUntil('\r');
              client.readStringUntil('\n');
              if (line.startsWith("--"+boundary)) break;
              if (argValue.length() > 0) argValue += "\n";
              argValue += line;
            }
#ifdef DEBUG_ESP_HTTP_SERVER
            DEBUG_OUTPUT.print("PostArg Value: ");
            DEBUG_OUTPUT.println(argValue);
            DEBUG_OUTPUT.println();
#endif

            RequestArgument& arg = postArgs[postArgsLen++];
            arg.key = argName;
            arg.value = argValue;

            if (line == ("--"+boundary+"--")){
#ifdef DEBUG_ESP_HTTP_SERVER
              DEBUG_OUTPUT.println("Done Parsing POST");
#endif
              break;
            }
          } else {
            _currentUpload.status = UPLOAD_FILE_START;
            _currentUpload.name = argName;
            _currentUpload.filename = argFilename;
            _currentUpload.type = argType;
            _currentUpload.totalSize = 0;
            _currentUpload.currentSize = 0;
#ifdef DEBUG_ESP_HTTP_SERVER
            DEBUG_OUTPUT.print("Start File: ");
            DEBUG_OUTPUT.print(_currentUpload.filename);
            DEBUG_OUTPUT.print(" Type: ");
            DEBUG_OUTPUT.println(_currentUpload.type);
#endif
            if(_currentHandler && _currentHandler->canUpload(_currentUri))
              _currentHandler->upload(*this, _currentUri, _currentUpload);
            _currentUpload.status = UPLOAD_FILE_WRITE;
            uint8_t argByte = _uploadReadByte(client);
readfile:
            while(argByte != 0x0D){
              if (!client.connected()) return _parseFormUploadAborted();
              _uploadWriteByte(argByte);
              argByte = _uploadReadByte(client);
            }

            argByte = _uploadReadByte(client);
            if (!client.connected()) return _parseFormUploadAborted();
            if (argByte == 0x0A){
              argByte = _uploadReadByte(client);
              if (!client.connected()) return _parseFormUploadAborted();
              if ((char)argByte != '-'){
                //continue reading the file
                _uploadWriteByte(0x0D);
                _uploadWriteByte(0x0A);
                goto readfile;
              } else {
                argByte = _uploadReadByte(client);
                if (!client.connected()) return _parseFormUploadAborted();
                if ((char)argByte != '-'){
                  //continue reading the file
                  _uploadWriteByte(0x0D);
                  _uploadWriteByte(0x0A);
                  _uploadWriteByte((uint8_t)('-'));
                  goto readfile;
                }
              }

              uint8_t endBuf[boundary.length()];
              client.readBytes(endBuf, boundary.length());

              if (strstr((const char*)endBuf, boundary.c_str()) != NULL){
                if(_currentHandler && _currentHandler->canUpload(_currentUri))
                  _currentHandler->upload(*this, _currentUri, _currentUpload);
                _currentUpload.totalSize += _currentUpload.currentSize;
                _currentUpload.status = UPLOAD_FILE_END;
                if(_currentHandler && _currentHandler->canUpload(_currentUri))
                  _currentHandler->upload(*this, _currentUri, _currentUpload);
#ifdef DEBUG_ESP_HTTP_SERVER
                DEBUG_OUTPUT.print("End File: ");
                DEBUG_OUTPUT.print(_currentUpload.filename);
                DEBUG_OUTPUT.print(" Type: ");
                DEBUG_OUTPUT.print(_currentUpload.type);
                DEBUG_OUTPUT.print(" Size: ");
                DEBUG_OUTPUT.println(_currentUpload.totalSize);
#endif
                line = client.readStringUntil(0x0D);
                client.readStringUntil(0x0A);
                if (line == "--"){
#ifdef DEBUG_ESP_HTTP_SERVER
                  DEBUG_OUTPUT.println("Done Parsing POST");
#endif
                  break;
                }
                continue;
              } else {
                _uploadWriteByte(0x0D);
                _uploadWriteByte(0x0A);
                _uploadWriteByte((uint8_t)('-'));
                _uploadWriteByte((uint8_t)('-'));
                uint32_t i = 0;
                while(i < boundary.length()){
                  _uploadWriteByte(endBuf[i++]);
                }
                argByte = _uploadReadByte(client);
                goto readfile;
              }
            } else {
              _uploadWriteByte(0x0D);
              goto readfile;
            }
            break;
          }
        }
      }
    }

    int iarg;
    int totalArgs = ((32 - postArgsLen) < _currentArgCount)?(32 - postArgsLen):_currentArgCount;
    for (iarg = 0; iarg < totalArgs; iarg++){
      RequestArgument& arg = postArgs[postArgsLen++];
      arg.key = _currentArgs[iarg].key;
      arg.value = _currentArgs[iarg].value;
    }
    if (_currentArgs) delete[] _currentArgs;
    _currentArgs = new RequestArgument[postArgsLen];
    for (iarg = 0; iarg < postArgsLen; iarg++){
      RequestArgument& arg = _currentArgs[iarg];
      arg.key = postArgs[iarg].key;
      arg.value = postArgs[iarg].value;
    }
    _currentArgCount = iarg;
    if (postArgs) delete[] postArgs;
    return true;
  }
#ifdef DEBUG_ESP_HTTP_SERVER
  DEBUG_OUTPUT.print("Error: line: ");
  DEBUG_OUTPUT.println(line);
#endif
  return false;
}

String WebServer::urlDecode(const String& text)
{
	String decoded = "";
	char temp[] = "0x00";
	unsigned int len = text.length();
	unsigned int i = 0;
	while (i < len)
	{
		char decodedChar;
		char encodedChar = text.charAt(i++);
		if ((encodedChar == '%') && (i + 1 < len))
		{
			temp[2] = text.charAt(i++);
			temp[3] = text.charAt(i++);

			decodedChar = strtol(temp, NULL, 16);
		}
		else {
			if (encodedChar == '+')
			{
				decodedChar = ' ';
			}
			else {
				decodedChar = encodedChar;  // normal ascii char
			}
		}
		decoded += decodedChar;
	}
	return decoded;
}

bool WebServer::_parseFormUploadAborted(){
  _currentUpload.status = UPLOAD_FILE_ABORTED;
  if(_currentHandler && _currentHandler->canUpload(_currentUri))
    _currentHandler->upload(*this, _currentUri, _currentUpload);
  return false;
}

WebServer.cpp

Arduino
/*
  WebServer.cpp - Dead simple web-server.
  Supports only one simultaneous client, knows how to handle GET and POST.

  Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/


#include <Arduino.h>
#include <libb64/cencode.h>
#include "WiFiServer.h"
#include "WiFiClient.h"
#include "WebServer.h"
#include "FS.h"
#include "detail/RequestHandlersImpl.h"

//#define DEBUG_ESP_HTTP_SERVER
#ifdef DEBUG_ESP_PORT
#define DEBUG_OUTPUT DEBUG_ESP_PORT
#else
#define DEBUG_OUTPUT Serial
#endif

const char * AUTHORIZATION_HEADER = "Authorization";

WebServer::WebServer(IPAddress addr, int port)
: _server(addr, port)
, _currentMethod(HTTP_ANY)
, _currentVersion(0)
, _currentStatus(HC_NONE)
, _statusChange(0)
, _currentHandler(0)
, _firstHandler(0)
, _lastHandler(0)
, _currentArgCount(0)
, _currentArgs(0)
, _headerKeysCount(0)
, _currentHeaders(0)
, _contentLength(0)
, _chunked(false)
{
}

WebServer::WebServer(int port)
: _server(port)
, _currentMethod(HTTP_ANY)
, _currentVersion(0)
, _currentStatus(HC_NONE)
, _statusChange(0)
, _currentHandler(0)
, _firstHandler(0)
, _lastHandler(0)
, _currentArgCount(0)
, _currentArgs(0)
, _headerKeysCount(0)
, _currentHeaders(0)
, _contentLength(0)
, _chunked(false)
{
}

WebServer::~WebServer() {
  if (_currentHeaders)
    delete[]_currentHeaders;
  _headerKeysCount = 0;
  RequestHandler* handler = _firstHandler;
  while (handler) {
    RequestHandler* next = handler->next();
    delete handler;
    handler = next;
  }
  close();
}

void WebServer::begin() {
  _currentStatus = HC_NONE;
  _server.begin();
  if(!_headerKeysCount)
    collectHeaders(0, 0);
}

bool WebServer::authenticate(const char * username, const char * password){
  if(hasHeader(AUTHORIZATION_HEADER)){
    String authReq = header(AUTHORIZATION_HEADER);
    if(authReq.startsWith("Basic")){
      authReq = authReq.substring(6);
      authReq.trim();
      char toencodeLen = strlen(username)+strlen(password)+1;
      char *toencode = new char[toencodeLen + 1];
      if(toencode == NULL){
        authReq = String();
        return false;
      }
      char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
      if(encoded == NULL){
        authReq = String();
        delete[] toencode;
        return false;
      }
      sprintf(toencode, "%s:%s", username, password);
      if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)){
        authReq = String();
        delete[] toencode;
        delete[] encoded;
        return true;
      }
      delete[] toencode;
      delete[] encoded;
    }
    authReq = String();
  }
  return false;
}

void WebServer::requestAuthentication(){
  sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
  send(401);
}

void WebServer::on(const String &uri, WebServer::THandlerFunction handler) {
  on(uri, HTTP_ANY, handler);
}

void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn) {
  on(uri, method, fn, _fileUploadHandler);
}

void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) {
  _addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
}

void WebServer::addHandler(RequestHandler* handler) {
    _addRequestHandler(handler);
}

void WebServer::_addRequestHandler(RequestHandler* handler) {
    if (!_lastHandler) {
      _firstHandler = handler;
      _lastHandler = handler;
    }
    else {
      _lastHandler->next(handler);
      _lastHandler = handler;
    }
}

void WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
    _addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
}

void WebServer::handleClient() {
  if (_currentStatus == HC_NONE) {
    WiFiClient client = _server.available();
    if (!client) {
      return;
    }

#ifdef DEBUG_ESP_HTTP_SERVER
    DEBUG_OUTPUT.println("New client");
#endif

    _currentClient = client;
    _currentStatus = HC_WAIT_READ;
    _statusChange = millis();
  }

  if (!_currentClient.connected()) {
    _currentClient = WiFiClient();
    _currentStatus = HC_NONE;
    return;
  }

  // Wait for data from client to become available
  if (_currentStatus == HC_WAIT_READ) {
    if (!_currentClient.available()) {
      if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) {
        _currentClient = WiFiClient();
        _currentStatus = HC_NONE;
      }
      yield();
      return;
    }

    if (!_parseRequest(_currentClient)) {
      _currentClient = WiFiClient();
      _currentStatus = HC_NONE;
      return;
    }
    _currentClient.setTimeout(HTTP_MAX_SEND_WAIT);
    _contentLength = CONTENT_LENGTH_NOT_SET;
    _handleRequest();

    if (!_currentClient.connected()) {
      _currentClient = WiFiClient();
      _currentStatus = HC_NONE;
      return;
    } else {
      _currentStatus = HC_WAIT_CLOSE;
      _statusChange = millis();
      return;
    }
  }

  if (_currentStatus == HC_WAIT_CLOSE) {
    if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) {
      _currentClient = WiFiClient();
      _currentStatus = HC_NONE;
    } else {
      yield();
      return;
    }
  }
}

void WebServer::close() {
  _server.end();
}

void WebServer::stop() {
  close();
}

void WebServer::sendHeader(const String& name, const String& value, bool first) {
  String headerLine = name;
  headerLine += ": ";
  headerLine += value;
  headerLine += "\r\n";

  if (first) {
    _responseHeaders = headerLine + _responseHeaders;
  }
  else {
    _responseHeaders += headerLine;
  }
}

void WebServer::setContentLength(size_t contentLength) {
    _contentLength = contentLength;
}

void WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
    response = "HTTP/1."+String(_currentVersion)+" ";
    response += String(code);
    response += " ";
    response += _responseCodeToString(code);
    response += "\r\n";

    if (!content_type)
        content_type = "text/html";

    sendHeader("Content-Type", content_type, true);
    if (_contentLength == CONTENT_LENGTH_NOT_SET) {
        sendHeader("Content-Length", String(contentLength));
    } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
        sendHeader("Content-Length", String(_contentLength));
    } else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client
      //let's do chunked
      _chunked = true;
      sendHeader("Accept-Ranges","none");
      sendHeader("Transfer-Encoding","chunked");
    }
    sendHeader("Connection", "close");

    response += _responseHeaders;
    response += "\r\n";
    _responseHeaders = String();
}

void WebServer::send(int code, const char* content_type, const String& content) {
    String header;
    // Can we asume the following?
    //if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
    //  _contentLength = CONTENT_LENGTH_UNKNOWN;
    _prepareHeader(header, code, content_type, content.length());
    _currentClient.write(header.c_str(), header.length());
    if(content.length())
      sendContent(content);
}

void WebServer::send_P(int code, PGM_P content_type, PGM_P content) {
    size_t contentLength = 0;

    if (content != NULL) {
        contentLength = strlen_P(content);
    }

    String header;
    char type[64];
    memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
    _prepareHeader(header, code, (const char* )type, contentLength);
    _currentClient.write(header.c_str(), header.length());
    sendContent_P(content);
}

void WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
    String header;
    char type[64];
    memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
    _prepareHeader(header, code, (const char* )type, contentLength);
    sendContent(header);
    sendContent_P(content, contentLength);
}

void WebServer::send(int code, char* content_type, const String& content) {
  send(code, (const char*)content_type, content);
}

void WebServer::send(int code, const String& content_type, const String& content) {
  send(code, (const char*)content_type.c_str(), content);
}

void WebServer::sendContent(const String& content) {
  const char * footer = "\r\n";
  size_t len = content.length();
  if(_chunked) {
    char * chunkSize = (char *)malloc(11);
    if(chunkSize){
      sprintf(chunkSize, "%x%s", len, footer);
      _currentClient.write(chunkSize, strlen(chunkSize));
      free(chunkSize);
    }
  }
  _currentClient.write(content.c_str(), len);
  if(_chunked){
    _currentClient.write(footer, 2);
  }
}

void WebServer::sendContent_P(PGM_P content) {
  sendContent_P(content, strlen_P(content));
}

void WebServer::sendContent_P(PGM_P content, size_t size) {
  const char * footer = "\r\n";
  if(_chunked) {
    char * chunkSize = (char *)malloc(11);
    if(chunkSize){
      sprintf(chunkSize, "%x%s", size, footer);
      _currentClient.write(chunkSize, strlen(chunkSize));
      free(chunkSize);
    }
  }
  _currentClient.write(content, size);
  if(_chunked){
    _currentClient.write(footer, 2);
  }
}


String WebServer::arg(String name) {
  for (int i = 0; i < _currentArgCount; ++i) {
    if ( _currentArgs[i].key == name )
      return _currentArgs[i].value;
  }
  return String();
}

String WebServer::arg(int i) {
  if (i < _currentArgCount)
    return _currentArgs[i].value;
  return String();
}

String WebServer::argName(int i) {
  if (i < _currentArgCount)
    return _currentArgs[i].key;
  return String();
}

int WebServer::args() {
  return _currentArgCount;
}

bool WebServer::hasArg(String  name) {
  for (int i = 0; i < _currentArgCount; ++i) {
    if (_currentArgs[i].key == name)
      return true;
  }
  return false;
}


String WebServer::header(String name) {
  for (int i = 0; i < _headerKeysCount; ++i) {
    if (_currentHeaders[i].key.equalsIgnoreCase(name))
      return _currentHeaders[i].value;
  }
  return String();
}

void WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
  _headerKeysCount = headerKeysCount + 1;
  if (_currentHeaders)
     delete[]_currentHeaders;
  _currentHeaders = new RequestArgument[_headerKeysCount];
  _currentHeaders[0].key = AUTHORIZATION_HEADER;
  for (int i = 1; i < _headerKeysCount; i++){
    _currentHeaders[i].key = headerKeys[i-1];
  }
}

String WebServer::header(int i) {
  if (i < _headerKeysCount)
    return _currentHeaders[i].value;
  return String();
}

String WebServer::headerName(int i) {
  if (i < _headerKeysCount)
    return _currentHeaders[i].key;
  return String();
}

int WebServer::headers() {
  return _headerKeysCount;
}

bool WebServer::hasHeader(String name) {
  for (int i = 0; i < _headerKeysCount; ++i) {
    if ((_currentHeaders[i].key.equalsIgnoreCase(name)) &&  (_currentHeaders[i].value.length() > 0))
      return true;
  }
  return false;
}

String WebServer::hostHeader() {
  return _hostHeader;
}

void WebServer::onFileUpload(THandlerFunction fn) {
  _fileUploadHandler = fn;
}

void WebServer::onNotFound(THandlerFunction fn) {
  _notFoundHandler = fn;
}

void WebServer::_handleRequest() {
  bool handled = false;
  if (!_currentHandler){
#ifdef DEBUG_ESP_HTTP_SERVER
    DEBUG_OUTPUT.println("request handler not found");
#endif
  }
  else {
    handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
#ifdef DEBUG_ESP_HTTP_SERVER
    if (!handled) {
      DEBUG_OUTPUT.println("request handler failed to handle request");
    }
#endif
  }

  if (!handled) {
    if(_notFoundHandler) {
      _notFoundHandler();
    }
    else {
      send(404, "text/plain", String("Not found: ") + _currentUri);
    }
  }

  _currentUri = String();
}

String WebServer::_responseCodeToString(int code) {
  switch (code) {
    case 100: return F("Continue");
    case 101: return F("Switching Protocols");
    case 200: return F("OK");
    case 201: return F("Created");
    case 202: return F("Accepted");
    case 203: return F("Non-Authoritative Information");
    case 204: return F("No Content");
    case 205: return F("Reset Content");
    case 206: return F("Partial Content");
    case 300: return F("Multiple Choices");
    case 301: return F("Moved Permanently");
    case 302: return F("Found");
    case 303: return F("See Other");
    case 304: return F("Not Modified");
    case 305: return F("Use Proxy");
    case 307: return F("Temporary Redirect");
    case 400: return F("Bad Request");
    case 401: return F("Unauthorized");
    case 402: return F("Payment Required");
    case 403: return F("Forbidden");
    case 404: return F("Not Found");
    case 405: return F("Method Not Allowed");
    case 406: return F("Not Acceptable");
    case 407: return F("Proxy Authentication Required");
    case 408: return F("Request Time-out");
    case 409: return F("Conflict");
    case 410: return F("Gone");
    case 411: return F("Length Required");
    case 412: return F("Precondition Failed");
    case 413: return F("Request Entity Too Large");
    case 414: return F("Request-URI Too Large");
    case 415: return F("Unsupported Media Type");
    case 416: return F("Requested range not satisfiable");
    case 417: return F("Expectation Failed");
    case 500: return F("Internal Server Error");
    case 501: return F("Not Implemented");
    case 502: return F("Bad Gateway");
    case 503: return F("Service Unavailable");
    case 504: return F("Gateway Time-out");
    case 505: return F("HTTP Version not supported");
    default:  return "";
  }
}

WebServer.h

Arduino
/*
  WebServer.h - Dead simple web-server.
  Supports only one simultaneous client, knows how to handle GET and POST.

  Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
*/


#ifndef WEBSERVER_H
#define WEBSERVER_H

#include <functional>
#include <WiFi.h>
#include <Update.h>
#include <WiFiUdp.h>

enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS };
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
                        UPLOAD_FILE_ABORTED };
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };

#define HTTP_DOWNLOAD_UNIT_SIZE 1460
#define HTTP_UPLOAD_BUFLEN 2048
#define HTTP_MAX_DATA_WAIT 1000 //ms to wait for the client to send the request
#define HTTP_MAX_POST_WAIT 1000 //ms to wait for POST data to arrive
#define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed
#define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection

#define CONTENT_LENGTH_UNKNOWN ((size_t) -1)
#define CONTENT_LENGTH_NOT_SET ((size_t) -2)

class WebServer;

typedef struct {
  HTTPUploadStatus status;
  String  filename;
  String  name;
  String  type;
  size_t  totalSize;    // file size
  size_t  currentSize;  // size of data currently in buf
  uint8_t buf[HTTP_UPLOAD_BUFLEN];
} HTTPUpload;

#include "detail/RequestHandler.h"

namespace fs {
class FS;
}

class WebServer
{
public:
  WebServer(IPAddress addr, int port = 80);
  WebServer(int port = 80);
  ~WebServer();

  void begin();
  void handleClient();

  void close();
  void stop();

  bool authenticate(const char * username, const char * password);
  void requestAuthentication();

  typedef std::function<void(void)> THandlerFunction;
  void on(const String &uri, THandlerFunction handler);
  void on(const String &uri, HTTPMethod method, THandlerFunction fn);
  void on(const String &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
  void addHandler(RequestHandler* handler);
  void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL );
  void onNotFound(THandlerFunction fn);  //called when handler is not assigned
  void onFileUpload(THandlerFunction fn); //handle file uploads

  String uri() { return _currentUri; }
  HTTPMethod method() { return _currentMethod; }
  WiFiClient client() { return _currentClient; }
  HTTPUpload& upload() { return _currentUpload; }

  String arg(String name);        // get request argument value by name
  String arg(int i);              // get request argument value by number
  String argName(int i);          // get request argument name by number
  int args();                     // get arguments count
  bool hasArg(String name);       // check if argument exists
  void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
  String header(String name);      // get request header value by name
  String header(int i);              // get request header value by number
  String headerName(int i);          // get request header name by number
  int headers();                     // get header count
  bool hasHeader(String name);       // check if header exists

  String hostHeader();            // get request host header if available or empty String if not

  // send response to the client
  // code - HTTP response code, can be 200 or 404
  // content_type - HTTP content type, like "text/plain" or "image/png"
  // content - actual content body
  void send(int code, const char* content_type = NULL, const String& content = String(""));
  void send(int code, char* content_type, const String& content);
  void send(int code, const String& content_type, const String& content);
  void send_P(int code, PGM_P content_type, PGM_P content);
  void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);

  void setContentLength(size_t contentLength);
  void sendHeader(const String& name, const String& value, bool first = false);
  void sendContent(const String& content);
  void sendContent_P(PGM_P content);
  void sendContent_P(PGM_P content, size_t size);

  static String urlDecode(const String& text);

template<typename T> size_t streamFile(T &file, const String& contentType){
  setContentLength(file.size());
  if (String(file.name()).endsWith(".gz") &&
      contentType != "application/x-gzip" &&
      contentType != "application/octet-stream"){
    sendHeader("Content-Encoding", "gzip");
  }
  send(200, contentType, "");
  return _currentClient.write(file);
}

protected:
  void _addRequestHandler(RequestHandler* handler);
  void _handleRequest();
  bool _parseRequest(WiFiClient& client);
  void _parseArguments(String data);
  static String _responseCodeToString(int code);
  bool _parseForm(WiFiClient& client, String boundary, uint32_t len);
  bool _parseFormUploadAborted();
  void _uploadWriteByte(uint8_t b);
  uint8_t _uploadReadByte(WiFiClient& client);
  void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength);
  bool _collectHeader(const char* headerName, const char* headerValue);

  struct RequestArgument {
    String key;
    String value;
  };

  WiFiServer  _server;

  WiFiClient  _currentClient;
  HTTPMethod  _currentMethod;
  String      _currentUri;
  uint8_t     _currentVersion;
  HTTPClientStatus _currentStatus;
  unsigned long _statusChange;

  RequestHandler*  _currentHandler;
  RequestHandler*  _firstHandler;
  RequestHandler*  _lastHandler;
  THandlerFunction _notFoundHandler;
  THandlerFunction _fileUploadHandler;

  int              _currentArgCount;
  RequestArgument* _currentArgs;
  HTTPUpload       _currentUpload;

  int              _headerKeysCount;
  RequestArgument* _currentHeaders;
  size_t           _contentLength;
  String           _responseHeaders;

  String           _hostHeader;
  bool             _chunked;

};


#endif //WebServer_H

all source code

Arduino
No preview (download only).

Credits

Patti U-aksorn
2 projects • 2 followers

Comments