Hi All,
I'm wondering if it's possible to force the closure of an MQX 3.8 httpd server session?
I have a cgi function that is called to service an http POST file upload from a form served by the httpd server. Things all seem to go well, I receive the file, do what I need to do with it and issue a response that basically says "I got the file, great job, thanks......" The response is properly displayed but the browser window showing the response page indicates it's waiting for something (that it never gets). Also, I think, the httpd server never closes the session. I think this because if I do this twice (2 is the max sessions value in the httpd server) the web server stops responding to all new requests until the system is rebooted.
I either need to figure out what is missing from my response (what is the browser window still waiting for) or how to force the termination of the session when this happens. Anybody have any experience in this area? Thanks!
~Tim
Solved! Go to Solution.
Problem solved,
As is usually the case, the problem wasn't where it appeared to be and had a much simpler explaination. The string buffer I was using to create the webpage reply to the http POST message was too small for the amount of data I Was trying to stuff into it and I overran it. Some days your the bat and some days......
Anyways, I have a properly working file upload from a MQX httpd served webpage to my TWR-MPC-5125 product. It was a pain to figure out so if anyone else needs to do this, reply here and I will post the server/client side code.
~Tim
Problem solved,
As is usually the case, the problem wasn't where it appeared to be and had a much simpler explaination. The string buffer I was using to create the webpage reply to the http POST message was too small for the amount of data I Was trying to stuff into it and I overran it. Some days your the bat and some days......
Anyways, I have a properly working file upload from a MQX httpd served webpage to my TWR-MPC-5125 product. It was a pain to figure out so if anyone else needs to do this, reply here and I will post the server/client side code.
~Tim
Tim,
I'm going to be doing something similar on a future release of my software. If you can provide some example code, I'd be very appreciative.
Thanks,
Neil
Hi Neil,
Glad to help. First, on your webpage that will upload the binary file, place a form with a file upload/selection object in it like this:
<form name="image_form" enctype="multipart/form-data" method="post" target="_blank" action="http:recv_image.cgi" onsubmit="upld_img_btn()"> <input title="Click to choose a file" type="file" id="img_file" name="img_file" /> <input type="button" value="Upload New Image" onclick="upld_img_btn()" /></form>
The action field "http:recv_image" specifies the name of the cgi function that will be called by the MQX httpd server on my project to receive and process the uploaded file. You can refer to the HVAC demo for examples of how to specify which cgi functions are called.
I also used a clientside Javascript function to actually perform the file upload from the webpage like this:
function upld_img_btn() { var filepath_str = document.getElementById("img_file").value if(filepath_str == "") { alert("No File Selected. Please Select A Firmware Image File First."); return; } var parsed = filepath_str.split( "\\" ); var iCount = parsed.length-1; var filename_only = parsed[iCount]; var sPromptString = "WARNING - About To Upload System Firmware Image File[ " + filename_only + " ] Please Confirm Or Cancel"; var retval=confirm(sPromptString); if(retval!=true) return; document.forms["image_form"].submit(); //Submit the image upload form data to the cgi function on your server return; }
Here's a stripped down version of the function running on my MQX project that is called by the MQX httpd server when the file is uploaded by the user. In my actual function, I use MQX functions to store the received file as a boot image in the image table and set it as active for the next reboot.
_mqx_int cgi_recv_image(HTTPD_SESSION_STRUCT *session) { boolean bMore2Come; uint_8 cByte; uint_16 iMsgIdStrngLength, iBoundaryLength; _mqx_int lReadLength, lTotalRecvdMsgBytes, lBytesRead, lFileByteSize, lCount, lTemp, lWriteSize, lRetval = 0; char_ptr sRecvBuffer = NULL; char_ptr sBufferPtr = NULL; char_ptr sBufferDataStartPtr = NULL; char_ptr sBufferEndPtr = NULL; char_ptr sTmpStrng = NULL; char sFileNameStrng[gcMAX_MSGSTRNGSIZE+1];//Allocate some temp buffers//---------------------------------------------------------------------------- lTotalRecvdMsgBytes = session->request.content_len; if(lTotalRecvdMsgBytes == 0) return(0); sRecvBuffer = _mem_alloc(lTotalRecvdMsgBytes); if(sRecvBuffer == NULL) { //... Handle error here return(0); } sTmpStrng = _mem_alloc(gc2K_STRING); //String needs to be large enough to store the http POST reply page if(sTmpStrng == NULL) { //... Handle error here return(0); }//Read in the data until all bytes (per lTotalRecvdMsgBytes value) are received//---------------------------------------------------------------------------- sBufferPtr = sRecvBuffer; //Use sBufferPtr as a movable temp pointer so sRecvBuffer can always point to buffer start lReadLength = httpd_read(session, sBufferPtr, lTotalRecvdMsgBytes); //Get any waiting bytes lBytesRead = lReadLength; //Record how many bytes we extracted from the incoming stream session->request.content_len -= lReadLength; while(session->request.content_len > 0) //Keep reading the incoming stream until all data bytes spec'd { //in session->request.content_len have been received sBufferPtr += lReadLength; //Advance buffer pointer to location where next bytes are to be stored lReadLength = httpd_read(session, sBufferPtr, (lTotalRecvdMsgBytes - lBytesRead)); //Retreive waiting bytes from incoming stream session->request.content_len -= lReadLength; lBytesRead += lReadLength; }//Example of header info that leads the http POST data sent by the webpage:////-----------------------------BoundaryString\r\nContent-Disposition: Form data: name="img_file"; filename="xxxxxxxx.xxx"\r\nContent-Type:application/octet stream\r\n\r\n//|_____________iBoundaryLength_____________| |variable| | variable | --Actual Start Of Data Payload Here->/// length length //Count the number of chars in the leading http encapsulation boundary string as per the above example://---------------------------------------------------------------------------- iBoundaryLength = 0; sBufferPtr = sRecvBuffer; //Reset sBufferPtr to start of sRecvBuffer while(*sBufferPtr != '\r') //Count the number of chars in the boundary string { //(String is finished at the \r\n chars) sBufferPtr++; iBoundaryLength++; }//Calculate the number of bytes of the file data payload in the received string//---------------------------------------------------------------------------- sBufferPtr = NULL; sBufferPtr = (char *)strstr(sRecvBuffer, "Content-Type"); //Find the start of the actual data which follows the Content-Type string if(sBufferPtr == NULL) { //... Handle error here _mem_free(sRecvBuffer); _mem_free(sTmpStrng); return(lRetval); } while(*sBufferPtr != '\r') //Count the number of chars in the Content-Type string sBufferPtr++; //(string is finished at the \r\n\r\n chars) sBufferPtr += 4; //Advance sBufferPtr past the \r\n\r\n chars to the actual start of the file data sBufferDataStartPtr = sBufferPtr; //Remember this location for later use lTemp = sBufferPtr - sRecvBuffer; //Determine how many chars preceeded the start of data lFileByteSize = lTotalRecvdMsgBytes - lTemp; //File size is total recvd bytes minus leading http header bytes lFileByteSize -= (iBoundaryLength + 6);//and trailing http closing boundary( "\r\n-----------------------------BoundaryString--\r\n" -END- )// |_____________iBoundaryLength_____________|//Do what you need to with the data//---------------------------------------------------------------------------- sBufferEndPtr = sBufferPtr + lFileByteSize; while(sBufferPtr < sBufferEndPtr) { }//Issue the all ok reply//---------------------------------------------------------------------------- sprintf(sTmpStrng, "Received %d Byte Firmware Image File[ %s ]\n", lFileByteSize, sFileNameStrng); strcat(sTmpStrng, "<br/><br/>\n"); strcat(sTmpStrng, "This new firmware image has been stored into flash and recorded in the Image Table\n"); strcat(sTmpStrng, "<br/>\n"); strcat(sTmpStrng, "as the currently active firmware image. When the device is restarted this new image\n"); strcat(sTmpStrng, "<br/>\n"); strcat(sTmpStrng, "will be loaded into memory and started. If a different boot image image is desired \n"); strcat(sTmpStrng, "<br/>\n"); strcat(sTmpStrng, "you may select that image from the Router Firmware Control webpage.\n"); lRetval = cgi_SendResponseWindow(session, sTmpStrng);//Clean up and exit happy here//---------------------------------------------------------------------------- _mem_free(sRecvBuffer); _mem_free(sTmpStrng); return(lRetval); }
Here's the cgi function that sends my reply to the web page. Since I added the "target="_blank" tag to my form, this reply will be displayed in a new window on the client computer.
_mqx_int cgi_SendResponseWindow(HTTPD_SESSION_STRUCT *session, char_ptr spString) { session->response.contenttype = CONTENT_TYPE_HTML; httpd_sendhdr(session, 0, 0); httpd_sendstr(session->sock, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">\n"); httpd_sendstr(session->sock, "<html>\n"); httpd_sendstr(session->sock, "<head>\n"); httpd_sendstr(session->sock, "<title>Firmware Image Upload Response</title>\n"); httpd_sendstr(session->sock, "<script type=\"text/javascript\">\n"); httpd_sendstr(session->sock, "function closeWin()\n"); httpd_sendstr(session->sock, " {\n"); httpd_sendstr(session->sock, " window.close();\n"); httpd_sendstr(session->sock, " return;\n"); httpd_sendstr(session->sock, " }\n"); httpd_sendstr(session->sock, "</script>\n"); httpd_sendstr(session->sock, "</head>\n"); httpd_sendstr(session->sock, "<body>\n"); httpd_sendstr(session->sock, "<br/><br/>\n"); httpd_sendstr(session->sock, spString); httpd_sendstr(session->sock, "<br/><br/>\n"); httpd_sendstr(session->sock, "<br/><br/>\n"); httpd_sendstr(session->sock, "<input type=\"button\" value=\"Close This Window\" onclick=\"closeWin()\" />\n"); httpd_sendstr(session->sock, "</body>\n"); httpd_sendstr(session->sock, "</html>\n"); return(session->request.content_len); }
I am pretty far from being an expert in this area so use this code at your own risk. It seems to work pretty well for me. Hope this helps.
Best Regards,
Tim
Hi Tim,
Thanks for your post on how to upload file through web page. I really looking for this solution. I will try to implement it. But through web page, is there any way to download the file from the TWR to the client PC?Hope you will help me on this one.
rgds,
Shark
I haven't had to do that yet Shark. I'm sure it's possible though. If you do implement that maybe you could post your solution here for all of us to share!
Best,
Tim