Updated Erel's module: for big data sending to a server i have added the sending POST request by chunks.
NOTICE: the code was and is based on Arduino's HTTPClient object that ... does not support "Transfer-Encoding: chunked" HTTP header ! If to add it by AddHeader method - a server's response is Error 400 (Bad request).
It's useful if MCU is generating big result data and RAM memory is not enough to save all the amount.
Now the big result can be sent to a server line by line during result generation.
NOTICE: the code was and is based on Arduino's HTTPClient object that ... does not support "Transfer-Encoding: chunked" HTTP header ! If to add it by AddHeader method - a server's response is Error 400 (Bad request).
Main module:
#Region Project Attributes
#AutoFlushLogs: True
#CheckArrayBounds: True
#StackBufferSize: 2000
#End Region
Sub Process_Globals
Public Serial1 As Serial
Private wifi As ESP8266WiFi
Private bc As ByteConverter
End Sub
Private Sub AppStart
Serial1.Initialize(115200)
Log("AppStart")
Delay(2000)
If wifi.Connect2("yourwifi", "yourpassword") Then
Log("Connected to router.")
Else
Log("Failed to connect to router.")
Return
End If
HttpJob.Initialize("ChunkedExample")
HttpJob.AddHeader("Content-Type", "application/x-www-form-urlencoded")
'HttpJob.AddHeader("Transfer-Encoding", "chunked") 'main trouble - this header is not supported by Arduino HTTPClient !!!
'Chunk start
Dim postData() As Byte = "postbig=ABCDEFGHIJKLMN¶m2=value2".GetBytes 'must be x-www-form-urlencoded
Dim totalLength As Int = postData.Length
Log("totalLength = ", totalLength)
HttpJob.PostChunkedStart("https://server.rcom/script.php")
'Sending a chunk of chunkLength in bytes
Dim i As Int = 0
Do While i < totalLength
Dim available As Int = totalLength - i
Log("available = ", available)
Dim chunkSize As Int = Min(HttpJob.chunkLength, available)
Log("chunkSize = ", chunkSize)
For j = 0 To HttpJob.chunkBuffer.Length - 1
HttpJob.chunkBuffer(j) = 0
Next
bc.ArrayCopy2(postData, i, HttpJob.chunkBuffer, 0, chunkSize)
HttpJob.PostChunkedSend(HttpJob.chunkBuffer, chunkSize)
If HttpJob.WorkingFlag = False Then 'error
Log("Chunked POST is interrupted !")
Exit
End If
i = i + chunkSize
Loop
HttpJob.PostChunkedFinish
'-----------------------------------
'HttpJob.Post("https://server.rcom/script.php", "postbig=ABCDEFGHIJKLMN") 'regular post POST for test
End Sub
Sub JobDone (Job As JobResult)
Log("*******************************")
Log("JobName: ", Job.JobName)
If Job.Success Then
Log("Response: ", bc.SubString2(Job.Response, 0, Min(700, Job.Response.Length)))
Else
Log("ErrorMessage: ", Job.ErrorMessage)
Log("Status: ", Job.Status)
Log(Job.Response)
End If
End Sub
HttpJob module:
'version 1.26
'added chunked POST request
'(c) Erel, Peacemakerv
Sub Process_Globals
Private requestCache(2500) As Byte
Private responseCache(1500) As Byte
Private responseIndex As Int
Private mJobname(32) As Byte
Private mVerb(8) As Byte
Private bc As ByteConverter
Private ssl As Boolean
Private port As Int
Private hostIndex, hostLen, pathIndex, pathLen, payloadIndex, payloadLen, headersIndex, headersLen As Int
Private astream As AsyncStreams
Private socket As WiFiSocket
Private sslsocket As WiFiSSLSocket
Public EOL() As Byte = Array As Byte(13, 10)
Type JobResult (JobName() As Byte, ErrorMessage() As Byte, Success As Boolean, Response() As Byte, Status As Int)
Private ResponseTimer As Timer
Public ResponseTimeout As UInt = 8000
Public WorkingFlag As Boolean
Public HttpErrorsCounter As ULong
Dim raf As RandomAccessFile
'For chunks
Private isChunked As Boolean
Public chunkLength As Int = 512 'needed to save, edit for your need
Public chunkBuffer(512) As Byte
End Sub
Public Sub Initialize(JobName As String)
WorkingFlag = True 'start working
bc.ArrayCopy(JobName, mJobname)
headersIndex = 0
headersLen = 0
ResponseTimer.Initialize("ResponseTimer_Tick", ResponseTimeout)
End Sub
Private Sub ResponseTimer_Tick
Log("Response timeout timer fired.")
ParseResult
End Sub
Public Sub AddHeader(Key() As Byte, Value() As Byte)
Dim b() As Byte = JoinBytes(Array(Key, ": ".GetBytes, Value, EOL))
bc.ArrayCopy2(b, 0, requestCache, headersIndex + headersLen, b.Length)
headersLen = headersLen + b.Length
End Sub
Public Sub Download (Link() As Byte)
ParseLink(Link, Null)
bc.ArrayCopy("GET", mVerb)
SendRequest(1)
End Sub
Public Sub Post (Link() As Byte, Payload() As Byte)
ParseLink(Link, Payload)
bc.ArrayCopy("POST", mVerb)
SendRequest(2)
End Sub
' === SendRequest with chunks support ===
Private Sub SendRequest (reqtype As Byte)
Dim host As String = bc.StringFromBytes(bc.SubString2(requestCache, hostIndex, hostIndex + hostLen))
Dim st As Stream = Null
Log(reqtype, ": trying to connect to: ", host, " port: ", port, " ssl: ", ssl)
If ssl Then
sslsocket.Close
If sslsocket.ConnectHost(host, port) Then
st = sslsocket.Stream
End If
Else
socket.Close
If socket.ConnectHost(host, port) Then
st = socket.Stream
End If
End If
If st = Null Then
SetError("Failed to connect")
WorkingFlag = False
Return
End If
Log("connected: ", host)
responseIndex = 0
astream.Initialize(st, "Astream_NewData", "Astream_Error")
astream.Write(mVerb).Write(" ").Write(bc.SubString2(requestCache, pathIndex, pathIndex + pathLen)).Write(" HTTP/1.1").Write(EOL)
astream.Write("Host: ").Write(host).Write(EOL)
'--------for chunks, manually inserted header --------------------
If isChunked Then
astream.Write("Transfer-Encoding: chunked").Write(EOL)
End If
'---------------------------------------
astream.Write("Connection: close").Write(EOL)
If isChunked = False Then
Dim payload() As Byte = bc.SubString2(requestCache, payloadIndex, payloadIndex + payloadLen)
If payload.Length > 0 Then
astream.Write("Content-Length: ").Write(NumberFormat(payload.Length, 0, 0)).Write(EOL)
End If
End If
If headersLen > 0 Then
astream.Write(bc.SubString2(requestCache, headersIndex, headersIndex + headersLen))
End If
astream.Write(EOL)
If isChunked = False Then
If payload.Length > 0 Then
astream.Write(payload)
End If
Log("HTTP request is sent OK: ", reqtype)
End If
End Sub
Private Sub AStream_NewData (Buffer() As Byte)
If ResponseTimer.Enabled = False Then ResponseTimer.Enabled = True
'Log("AStream_NewData: ", Buffer)
If responseIndex + Buffer.Length > responseCache.Length Then
Log("ResponseCache is full (", Buffer.Length, ")")
WorkingFlag = False
Return
End If
bc.ArrayCopy2(Buffer, 0, responseCache, responseIndex, Buffer.Length)
responseIndex = responseIndex + Buffer.Length
End Sub
Private Sub AStream_Error
ParseResult
End Sub
Private Sub ParseResult
ResponseTimer.Enabled = False
If responseIndex = 0 Then
Log("ParseResult: Response not available.")
SetError("Response not available.")
WorkingFlag = False
Return
End If
Dim response() As Byte = bc.SubString2(responseCache, 0, responseIndex)
Dim i As Int = bc.IndexOf(response, EOL)
Dim statusLine() As Byte = bc.SubString2(response, 0, i)
Dim i1 As Int = bc.IndexOf(statusLine, " ")
Dim i2 As Int = bc.IndexOf2(statusLine, " ", i1 + 1)
Dim status As Int = bc.StringFromBytes(bc.SubString2(statusLine, i1 + 1, i2))
If Floor(status / 100) = 3 Then 'handle redirections
i1 = bc.IndexOf(response, "Location:")
If i1 > -1 Then
i2 = bc.IndexOf2(response, EOL, i1 + 1)
Dim NewPath() As Byte = bc.Trim(bc.SubString2(response, i1 + 9, i2))
Log("Redirecting to: ", NewPath)
ParseLink(NewPath, bc.SubString2(requestCache, payloadIndex, payloadIndex + payloadLen))
CallSubPlus("SendRequest", 1, 0) 'to avoid stack overflows
Return
End If
End If
Dim jr As JobResult
jr.Success = Floor(status / 100) = 2
i = bc.IndexOf(response, Array As Byte(13, 10, 13, 10))
jr.Response = bc.SubString(response, i + 4)
jr.JobName = mJobname
jr.ErrorMessage = Array As Byte()
jr.Status = status
isChunked = False
HttpErrorsCounter = 0 'reset
webapi.JobDone(jr)
WorkingFlag = False
End Sub
Private Sub ParseLink (Link() As Byte, Payload() As Byte)
Dim hostStart As Int
If bc.StartsWith(Link, "https://") Then
ssl = True
hostStart = 8
Else if bc.StartsWith(Link, "http://") Then
ssl = False
hostStart = 7
Else
Log("Invalid link")
SetError("Invalid link")
WorkingFlag = False
Return
End If
Dim i As Int = bc.IndexOf2(Link, "/", hostStart)
Dim path() As Byte
If i = -1 Then
i = Link.Length
path = "/"
End If
Dim host() As Byte = bc.SubString2(Link, hostStart, i)
If i < Link.Length Then path = bc.SubString(Link, i)
Dim colonStart As Int = bc.IndexOf(host, ":")
If colonStart > -1 Then
port = bc.StringFromBytes(bc.SubString(host, colonStart + 1))
host = bc.SubString2(host, 0, colonStart)
Else
If ssl Then port = 443 Else port = 80
End If
SetRequestCache(host, path, Payload)
End Sub
Private Sub SetRequestCache(host() As Byte, path() As Byte, payload() As Byte)
If payload = Null Then payload = Array As Byte()
payloadIndex = headersIndex + headersLen
payloadLen = payload.Length
bc.ArrayCopy2(payload, 0, requestCache, payloadIndex, payloadLen)
hostIndex = payloadIndex + payloadLen
hostLen = host.Length
bc.ArrayCopy2(host, 0, requestCache, hostIndex, hostLen)
pathIndex = hostIndex + hostLen
pathLen = path.Length
bc.ArrayCopy2(path, 0, requestCache, pathIndex, pathLen)
End Sub
Private Sub SetError (msg() As Byte)
HttpErrorsCounter = HttpErrorsCounter + 1
Dim jr As JobResult
jr.JobName = mJobname
jr.ErrorMessage = msg
jr.Response = Array As Byte()
jr.Success = False
jr.Status = 0
webapi.JobDone(jr)
WorkingFlag = False
End Sub
' === New methods for chunked POST ===
Public Sub PostChunkedStart(Link() As Byte)
isChunked = True
Log("Starting chunk POST...")
ParseLink(Link, Null)
bc.ArrayCopy("POST", mVerb)
SendRequest(0)
End Sub
Public Sub PostChunkedSend(Buffer() As Byte, Length As Int)
If Length <= 0 Then
Log("Zero length, exit")
Return
End If
chunkLength = Length
isChunked = True
'Log("Chunk being sent now: ", Buffer)
Dim hexLength() As Byte = ToHexString(chunkLength)
astream.Write(hexLength).Write(EOL)
astream.Write2(Buffer, 0, Length).Write(EOL)
Log("Chunk is sent")
chunkLength = 512 'restore1
End Sub
Public Sub PostChunkedFinish
Log("Chunks are finished")
chunkLength = 512 'restore2
astream.Write("0".GetBytes).Write(EOL).Write(EOL)
isChunked = False
Log("Chunk posting is finished")
End Sub
'hex number
Sub ToHexString(value As Int) As Byte()
Dim b(2) As Byte
raf.Initialize(b, False) 'big endian
raf.WriteInt16(value, 0)
Dim res() As Byte = bc.HexFromBytes(b)
'Log("Hex chunk length = ", res)
Return res
End Sub
Writing at 0x0010dc64... (100 %)
Wrote 1049056 bytes (644586 compressed) at 0x00010000 in 7.6 seconds (effective 1097.5 kbit/s)...
Hash of data verified.
Leaving...
Hard resetting with RTC WDT...
New upload port: COM13 (serial)
Connected to router.
totalLength = 36
Starting chunk POST...
trying to connect to: pmaker.ru port: 443 ssl: 1
connected: pmaker.ru
available = 36
chunkSize = 7
Chunk being sent now: postbig
Hex chunk length = 0007
Chunk is sent
available = 29
chunkSize = 7
Chunk being sent now: =ABCDEF
Hex chunk length = 0007
Chunk is sent
available = 22
chunkSize = 7
Chunk being sent now: GHIJKLM
Hex chunk length = 0007
Chunk is sent
available = 15
chunkSize = 7
Chunk being sent now: N¶m
Hex chunk length = 0007
Chunk is sent
available = 8
chunkSize = 7
Chunk being sent now: 2=value
Hex chunk length = 0007
Chunk is sent
available = 1
chunkSize = 1
Chunk being sent now: 2
Hex chunk length = 0001
Chunk is sent
Chunks are finished
Chunk posting is finished
*******************************
JobName: ChunkedExample
Response: Firstly $_SERVER['HTTP_TRANSFER_ENCODING']:
$_POST:
Array
(
[postbig] => ABCDEFGHIJKLMN
[param2] => value2
)
'php://input':
postbig=ABCDEFGHIJKLMN¶m2=value2
It's useful if MCU is generating big result data and RAM memory is not enough to save all the amount.
Now the big result can be sent to a server line by line during result generation.
Server's PHP script to get the data:
<?php
echo("Firstly \$_SERVER['HTTP_TRANSFER_ENCODING']:\r\n");
print_r($_SERVER['HTTP_TRANSFER_ENCODING']);
echo("\r\n\r\n");
echo("\$_POST:\r\n");
print_r($_POST);
echo("\r\n\r\n");
$data = file_get_contents('php://input');
echo("'php://input':\r\n");
echo("\r\n\r\n");
print_r($data);
?>
Last edited: