A great majority of the requests done through HTTP result in a response; there are two different ways of accessing the response depending on its size:
-
GetResponseData
for small data (less than 20MB) and -
ReadData
for anything larger
The GetResponseData
is mostly used for
simple requests that don't return a lot of data. It's regularly used
for API requests that expect short responses, for example:
HTTPClient ino_client '''' rem n_cst_azurefs.of_getcontainers:13 li_res = ino_client.Sendrequest( "GET", of_geturl(URL_LIST_CONTAINERS) ) li_responseStatus = ino_client.GetResponseStatuscode( ) if li_responseStatus <> 200 then as_error = ino_client.GetResponseStatusText( ) return 1 end if ino_client.GetResponseBody(ref lblb_responseBytes) ls_response = string(lblb_responseBytes, EncodingUTF8!)
The GetResponseData
can read data onto
both a string or a blob, so it's perfect for small data.
However, this method does not support working with data larger than 20MB.
The ReadData
method is used to read the
response in chunks, thus allowing the read of arbitrarily large
responses. This is the method used to download the files in the
sample application. Since the data to be read may be larger than
what a Blob object can hold, it's recommended that the chunks be
written to a file on disk as they're read.
Note: It is necessary to stop the HTTPClient object from automatically loading the data:
''' n_cst_filetransferhelper.of_download:25 lb_autoread = ino_client.Autoreaddata ino_client.Autoreaddata = false
Then we can send the request for the data:
li_res = ino_client.SendRequest( "GET", as_url)
Finally, we read and send to disk the received data, reading
chunk by chunk. The ReadData
method
automatically reads the data following what was previously read. The
FileWriteEx
too writes at the end of what was
previously written.
''' n_cst_filetransferhelper.of_download:29 do while true lblb_buffer = Blob("") li_ret = ino_client.readdata(ref lblb_buffer, CHUNK_SIZE) FileWriteEx(al_file, lblb_buffer) ll_chunksWritten++ ... loop
Don't forget to restore the value of
AutoReadData
:
ino_client.Autoreaddata = lb_autoread
Note: Reading and writing to disk by chunks might take a long time, so it's recommended to yield() between chunks to prevent the application from freezing.
For the Azure example, the Azure REST API currently only allows to upload files through PUT requests, and currently the HTTP Client doesn't have a supported way to upload files larger than 20MB with a single call. Thus, to upload our files we have to upload files in chunks, making use of Azure's Put Block and Put Block List endpoints to submit the files in parts.
To read the file in parts the FileReadEx in PowerScript Reference method is used.
''' n_cst_azurefs.of_uploadfile:28 ll_filesize = FileLength64(as_path) li_file = FileOpen(as_path, StreamMode!, Read!, LockRead!) if li_file < 1 then as_error = "Could not open file for reading" return -1 end if li_chunk = 0 do while true ll_read = FileReadEx(li_file, ref lblb_buffer, CHUNK_SIZE) if ll_read < 1 then exit lblb_pathHash = lno_crypter.SHA(SHA512!, Blob(as_path + string(li_chunk), EncodingUTF8!)) ls_blockId = lno_coderobject.base64encode(lblb_pathHash) ls_blockId = Left(ls_blockId, 64) li_res = of_putblock(& lblb_buffer,& ls_sanitizedObjectPath,& lno_coderobject.urlencode(Blob(ls_blockId, EncodingUTF8!)),& ref ls_error)
Each chunk of the file will have an ID generated (this is needed to commit all the pieces into a single file on Azure) and immediately after, the chunk will be uploaded.
To upload a chunk of the file, all that's necessary is to send a PUT request to the URL appending the Blob as an argument to the SendRequest method:
''' n_cst_filetransferhelper.of_uploadPut:7 int li_res int li_responseCode string ls_responseText ino_client.SetRequestheader( "Content-Length", String(Len(ablb_blob)), true) li_res = ino_client.SendRequest("PUT", as_url, ablb_blob) if li_res <> 1 then as_error = "couldn't send request" return -1 end if li_responseCode = ino_client.GetResponseStatuscode( ) ls_responseText = ino_client.GetResponseStatusText( ) if li_responseCode < 200 or li_responseCode > 300 then as_error = "Received non-success status code " + string(li_responseCode) + ": " + ls_responseText return -1 end if return 1
Once all the chunks have been uploaded and the IDs collected, we have to send another request indicating the instruction to compile all the chunks into a single file. The list of chunks is built as an XML document, thus we have to indicate such in the request header (with SetRequestHeader).
''' n_cst_azurefs.of_putblocklist:8 ls_xml = "<?xml version='1.0' encoding='utf-8'?><BlockList>" for i = 1 to UpperBound(as_blockids) ls_xml += "<Uncommitted>" + as_blockids[i] + "</Uncommitted>" next ls_xml += "</BlockList>" of_prepareclient( "PUT", len(ls_xml), "/" + is_accname + "/" + is_container + "/" + as_destination, "comp:blocklist", "application/xml") ino_client.SetRequestheader( "Content-Type", "application/xml", true) li_res = ino_client.SendRequest("PUT", & ls_url,& ls_xml)
Finally we send the PUT request to the URL and pass the XML as the body of the request.
The procedure above is used to work with Azure's particular constraints; however a more general approach is to upload a file in chunks using a POST method. We use this other approach with the C# file server.
/// n_cst_genericfileserverbrowser.of_uploadfile:37 li_res = ino_client.postdatastart(ls_url) // initiate the chunked requeset if li_res <> 1 then as_error = "Error attempting file transfer" return -1 end if do while lll_transferred < ll_fileSize lblb_buffer = Blob("") li_res = FileReadEx(li_handle, lblb_buffer, CHUNK_SIZE) // read a chunk of the data if li_res = 0 then exit if li_res < 1 then as_error = "Error reading file chunk" return -1 end if ino_client.postdata( lblb_buffer, li_res) // send the chunk of the data lll_transferred += li_res loop // repeat until all the file has been read li_res = ino_client.postdataend()
This is the way to send very large files to an endpoint expecting a POST action, there's currently not an equivalent operation for the PUT method.
The demo application contains an NVO with the logic required to download and upload files through HTTP called n_cst_filetransferhelper. This tool will help you work with downloading/uploading files by taking care of the nitty-gritty and providing a single method for each operation. Feel free to download it and use it in your own projects and/or modify it to better accommodate your requirements.