Upload File to a Server using HTTP possible?
| Tue, 2005-02-08 16:58 | |
|
Hi guys,
I will like to ask if it is possible to upload a file from the mobile device to a server (assuming I'm using a servlet to receive incoming file) using HTTP? Is it possible to provide me with a little sample code, as I'm very new to the Symbian world. I tried searching the API documentation, but I cannot find any section about HTTP at all. I don't know why, because I was told that a library existed. I'm using the Series 60 2.0 SDK, by the way. Thank you very much for your attention. |
|






Forum posts: 18
Forum posts: 31
// methods inherited from MHTTPDataSupplier
virtual TBool GetNextDataPart(TPtrC8& aDataPart);
virtual void ReleaseData();
virtual TInt OverallDataSize();
virtual TInt Reset();
// methods from MHTTPTransactionCallback
virtual void MHFRunL(RHTTPTransaction aTransaction, const THTTPEvent& aEvent);
virtual TInt MHFRunError(TInt aError, RHTTPTransaction aTransaction, const THTTPEvent& aEvent);
{
TBool retVal = EFalse;
aDataPart.Set(iReqBodySubmitBufferPtr);
retVal = (iReqBodySubmitBufferPtr.Length() == 0);
return retVal;
}
void CMECKMAppUi::ReleaseData()
{
TPtr8 buff = iOriginalBuffer->Des();
buff.Zero();
}
TInt CMECKMAppUi::OverallDataSize()
{
return iHTTPBodySize;
}
TInt CMECKMAppUi::Reset()
{
return KErrNotSupported;
}
TInt CMECKMAppUi::MHFRunError(TInt aError, RHTTPTransaction /*aTransaction*/, const THTTPEvent& /*aEvent*/)
{
return aError;
}
So when you get a response MHFRunL is called once for headers, and once more shortly after for the body. Notice how RHTTPResponse::Body() has the same methods that you've earlier overloaded?(e.g. GetNextDataPart(), ReleaseData(), OverallDataSize(), Reset()). You use them to get the response.
{
switch (aEvent.iStatus)
{
case KErrCancel:
{
// User cancelled dialog DISP error dialog R_BACKUP_CANCELLED_MSG_8
ibolHTTPSend = KErrCancel;
}break;
case THTTPEvent::EGotResponseHeaders:
{
// HTTP response headers have been received. We can determine now if there is
// going to be a response body to save.
RHTTPResponse resp = aTransaction.Response();
TInt status = resp.StatusCode();
RStringF statusStr = resp.StatusText();
TBuf<32> statusStr16;
statusStr16.Copy(statusStr.DesC());
if (resp.HasBody() && (status >= 200) && (status < 300) && (status != 204))
{
TInt dataSize = resp.Body()->OverallDataSize();
if (dataSize >= 0)
dataSize = dataSize;
else
dataSize = 0;
}
} break;
case THTTPEvent::EGotResponseBodyData:
{
// Get the body data supplier
iRespBody = aTransaction.Response().Body();
TPtrC8 bodyData;
iRespBody->GetNextDataPart(bodyData);
iBodyBuffer = HBufC8::NewMaxL(iRespBody->OverallDataSize());
iBodyBufferPtr.Set(iBodyBuffer->Des());
iBodyBufferPtr.Copy(bodyData);
// Done with that bit of body data
iRespBody->ReleaseData();
aTransaction.Close();
CActiveScheduler::Stop();
if(iBodyBufferPtr.Compare(KErrWriteString) != 0 && iBodyBufferPtr.Length() <= 5)
{
TLex8 iLex8ResponseCRC(iBodyBufferPtr);
intCRCResponse = 0;
iResponseCRCMatch = EFalse;
iLex8ResponseCRC.Val(intCRCResponse, EDecimal);
iResponseCRCMatch = (intCRCResponse == iOriginalCRC);
if (iResponseCRCMatch != EFalse)
{
// DISP OK & Closing DLG
WriteToUserLog();
HandleRecognizer(EFalse); // Remove Recognizer from System\Recogs
ShowDialogL(12); // DISP dialog R_BACKUP_OK_MSG_12
ibolHTTPSend = KErrNone;
}
}
if (iResponseCRCMatch == EFalse || iBodyBufferPtr.Compare(KErrWriteString) == 0 || iBodyBufferPtr.Length() > 5)
{
ShowDialogL(11); // DISP Error dialog R_BACKUP_FAILED_MSG_11
ibolHTTPSend = KErrCancel;
}
} break;
case THTTPEvent::EResponseComplete:
{
} break;
case THTTPEvent::ESucceeded:
{
aTransaction.Close();
CActiveScheduler::Stop();
} break;
case THTTPEvent::EFailed:
{
aTransaction.Close();
CActiveScheduler::Stop();
} break;
case THTTPEvent::ERedirectedPermanently:
{
} break;
case THTTPEvent::ERedirectedTemporarily:
{
} break;
default:
{
if (aEvent.iStatus < 0)
{
aTransaction.Close();
CActiveScheduler::Stop();
ShowDialogL(11); // DISP Error dialog R_BACKUP_FAILED_MSG_11
ibolHTTPSend = KErrCancel;
}
} break;
}
}
1. Open a Session.
2. Get a pointer to StringPool.
3. Set a pointer to the CallBack class implementing the MHTTPTransactionCallback. In my case I've used the CMECKMAppUi which is actually my AppUI class (CEikAppUi), but you can make a class handling just HTTP if you wan to.
4. Set the HTTP method (e.g. POST).
5. Open a RHTTPTransaction with the relevant method.
6. Set the headers with SetHeaderL() which is described in \examples\appprots\exampleclient. SetMultiPartHeader() is where I set my multipart/form-data types. (I'll come back to it later)
7. Set the request object Body.
8. SetHTTPBody() get the size that I'm counting myself byte by byte. It also copies data to my iReqBodySubmitBuffer[Ptr] where my pointer is pointing to. You dont need to know that.
9. Call SubmitL().
10. Start the active scheduler. (I hope you know that you have to create one. In UIKON you have a default active scheduler a static CActiveScheduler::Current(). Use that one and make a pointer to it.
RHTTPTransaction iTrans;
RHTTPHeaders iHTTTPHeader;
RStringPool iStringPool;
RStringF iHTTPPostMethod;
iSess.OpenL();
iStringPool = iSess.StringPool();
iTransObs = (MHTTPTransactionCallback*) this;
iHTTPPostMethod = iStringPool.StringF(HTTP::EPOST,RHTTPSession::GetTable());
iTrans = iSess.OpenTransactionL(iUploadURI, *iTransObs, iHTTPPostMethod);
iHTTPPostMethod.Close();
iHTTTPHeader = iTrans.Request().GetHeaderCollection();
SetMultiPartHeader();
// Add headers appropriate to all methods
SetHeaderL(iHTTTPHeader, HTTP::EUserAgent, KUserAgent);
SetHeaderL(iHTTTPHeader, HTTP::EAccept, KAccept);
SetHeaderL(iHTTTPHeader, HTTP::EContentType, iHTTPMultiPartHeader);
MHTTPDataSupplier* dataSupplier = this;
iTrans.Request().SetBody(*dataSupplier);
iHTTPBodySize = SetHTTPBody();
if(iHTTPBodySize == 0) User::Leave(KErrCorrupt); // DISP Error Dialog (e.g.. Shouldn't have to reach this point)
iTrans.SubmitL();
CActiveScheduler::Start();
http://ietf.org/rfc/rfc2616.txt?number=2616
http://ietf.org/rfc/rfc1867.txt?number=1867
Let's look at the Multipart thing:
SetBoundary() creates a pseudo random boundary in the iHTTPBoundary buffer. You have to count all the bytes manually, cause they will end up in the body.
Let's have a lecture on multipart/form-data. You'll need that boundayre I'm talking about when you set your header with SetHeaderL() earlier with iHTTPMultiPartHeader. The boundary itself can be any string (8 bits chars); boundary=ABCD. Your iHTTPMultiPartHeader should then be; "multipart/form-data; boundary=ABCD" and that is your overall content type.
Ok, the boundary is ABCD, but in the body it will be preceded by "--", so
it will look like --ABCD followed by carriage return and linefeed "\r\n".
The you have to add the Content-Disposition: (e.g. a header but in multipart/form-data you have to add it in the body, cause its different for every file), next I add a line with KDispFormData and "\r\n" the integer value (in my case), followed by "\r\n".
I add a new boundary --ABCD + "\r\n". Once more KDisposition followed by KDispUserFile followed by the filename and terminated with a singlequote and ofcourse "\r\n".
Next is the content type for the file itself KMIMETextPlain +"\r\n". Then the bytes in the file youÂ’d like to send, followed by "\r\n". When your done you add the finishing boundary and that has to end with an "--", so --ABCD--
If your not done you keep on adding elements, files separating them with boundary string and all the "\r\n" are vey important, if you miss one, it all goes down the drain. Finish it of with boundary-- (e.g. --ABCD--)
_LIT8(KMIMEMultipart, "multipart/form-data; boundary=");
_LIT8(KDispUserFile, "form-data; name='userfile'; filename='");
_LIT8(KDispFormData, "form-data; name='CRC_SYMBIAN'");
_LIT8(KDisposition, "Content-Disposition: ");
_LIT8(KCrLf, "\r\n");
_LIT(KBoundaryEnd, "--");
void CMECKMAppUi::SetMultiPartHeader()
{
SetBoundary();
iHTTPMultiPartHeader.Zero();
iHTTPMultiPartHeader.Append(KMIMEMultipart);
iHTTPMultiPartHeader.Append(iHTTPBoundary);
}
TInt CMECKMAppUi::SetHTTPBody()
{
_LIT8(KSingleQuote, "'");
iReqBodySubmitBuffer = HBufC8::NewMaxL(iOriginalBufferPtr.Length() + (iHTTPBoundary.Length()*3) +512);
iReqBodySubmitBufferPtr.Set(iReqBodySubmitBuffer->Des());
iReqBodySubmitBufferPtr.Zero();
iReqBodySubmitBufferPtr.Append(KBoundaryEnd);
iReqBodySubmitBufferPtr.Append(iHTTPBoundary);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(KDisposition);
iReqBodySubmitBufferPtr.Append(KDispFormData);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.AppendNum((int)iOriginalCRC);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(KBoundaryEnd);
iReqBodySubmitBufferPtr.Append(iHTTPBoundary);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(KDisposition);
iReqBodySubmitBufferPtr.Append(KDispUserFile);
iReqBodySubmitBufferPtr.Append(iSystemCopyFileName);
iReqBodySubmitBufferPtr.Append(KSingleQuote);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(KMIMETextPlain);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(iOriginalBufferPtr);
iReqBodySubmitBufferPtr.Append(KBoundaryEnd);
iReqBodySubmitBufferPtr.Append(iHTTPBoundary);
iReqBodySubmitBufferPtr.Append(KBoundaryEnd);
return iReqBodySubmitBufferPtr.Length();
}
Content-Type: multipart/form-data; boundary=ABCD
User-Agent: My Symbian Explorer
Host: www.kundutech.com
Content-Length: 628
Connection: Keep-Alive
Cache-Control: no-cache
--ABCD
Content-Disposition: form-data; name="CRC_SYMBIAN"
12345
--ABCD
Content-Disposition: form-data; name="userfile"; filename="C:\test.txt"
Content-Type: text/plain
My text goes here and it originates from a file on Symbian.
--ABCD--
What you need to know now is. If your GetNextDataPart() is not working properly, the HTTP Host header (look in RFC) will not be added automatically and you'll get Missing Host Header error -7361 (or something like that).
So what happens when youÂ’re active scheduler is called?
1. YOUR virtual OverallDataSize() is called (several times).
2. GetNextDataPart() is called until the buffer is empty (e.g. GetNextDataPart return EFalse)
3. You get a response (hopefully HTTP 200 OK), the MFRunL is called once for headers, once for the body.
You're OverallDataSize() is called then all the initial things are working, remember that it has to respond with the correct content-length otherwise if it's to short all the bytes wonÂ’t be sent, and if it's to long you'll end up with a KErrCorrupt.
If GetNextDataPart() is called and it provides the correct data, then it should work.
http://www.kundutech.com
Forum posts: 7
i see your post was a good while ago. But i'm trying to use your ideas to make a multipart HTTP post.
I was wondering if you could explain some of the variables you used in example?
-iOriginalBuffer
-iReqBodySubmitBufferPtr
-iOriginalCRC
Your help would be greatly appreciated
Thanks,
Dermot.
Forum posts: 31
The iOriginalBuffer is the content of the file being sent. Look at this:
The packet must include the length using “Content-Length” header and a content type using the “Content-Type” header. While the length is calculated in a pretty straight forward manner the content type for uploading file over HTTP is described in RFC 1867. This header consist of two part, the type it self, “multipart/form-data” and the boundary.
The boundary is used to delimit different parts of data being upload and it is preceded by two dashes “--“. After the boundary each part is described using the “Content-Disposition” header telling the server that it is a “form-data”, not any of the other MIME types, and its name. The header is then followed by two sets of carriage return and linefeed “\r\n\r\n” and then the value. The next part is delimited by the value of boundary preceded by “--”.
Sending a file also includes the “filename” or actually the full path and filename of the file being sent in the “Content-Disposition” header. The header is then followed by a “Content-Type” header for the file it self, which in the example below is “text/plain”. Then the packet has two more sets of carriage return and linefeed followed by the actual content of the file.
When the last part of the multipart/form-data has been added to the packet it is ended with the two dashes, boundary and finally another two dashes.
HTML Code:<FORM ACTION="http://www.kundutech.com/upload.asp"
   ENCTYPE="multipart/form-data"
   METHOD=POST>
What is your name? <INPUT TYPE=TEXT NAME=submitter>
What files are you sending? <INPUT TYPE=FILE NAME=pics>
</FORM>
iReqBodySubmitBufferPtr corrensponds to the entire request:
POST /upload.asp HTTP/1.1
Host: www.kundutech.com
User-Agent: XXX HTTP
Content-Length: 709
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x
content-disposition: form-data; name="field1"
Joe Blow
--AaB03x
content-disposition: form-data; name="pics"; filename="file1.txt"
Content-Type: text/plain
... contents of file1.txt ...
--AaB03x—
Now iOriginalCRC is a CRC value that I need as a field, but in the above example it corresponds to the string "Joe Blow".
iOriginalBuffer corresponds to contents of file1.txt .
Rewriting it would result in iReqBodySubmitBufferPtr =:
[i]POST /upload.asp HTTP/1.1
Host: www.kundutech.com
User-Agent: XXX HTTP
Content-Length: 709
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x
content-disposition: form-data; name="field1"
iOriginalCRC
--AaB03x
content-disposition: form-data; name="pics"; filename="file1.txt"
Content-Type: text/plain
iOriginalBuffer
--AaB03x—[/b]
I hope that it clear's things up and for more detail look at RFC 1867.
Cheers,
Piotr
www.kundutech.com
PS. Please make a new post if you solved your problem and most importantly how?
http://www.kundutech.com
Forum posts: 59
Can in the same way I upload an image file also.
Regards.
Forum posts: 31
Sure you can, it's the same thing.
http://www.kundutech.com
Forum posts: 59
http://discussion.forum.nokia.com/forum/showthread.php?t=82021&highlight=HTTP
That I need to read it into a buffer and then convert it to base 64.
Is that required ?
Also If I run the HttpCLientExample in approots, I can do a get but the posting of a file is failing with -20.
Can you provide some pointers to it.
Thanks.
Forum posts: 59
I am getting a -20 error.
Attached is the code snippet:
void CClientEngine::SetMultiPartHeader()
{
iHTTPBoundary.Copy(_L8("ABCD"));
iHTTPMultiPartHeader.Zero();
iHTTPMultiPartHeader.Append(KMIMEMultipart);
iHTTPMultiPartHeader.Append(iHTTPBoundary);
}
TInt CClientEngine::SetHTTPBody()
{
_LIT8(KSingleQuote, "'");
TInt err = iReqBodyFile.Read(iPostData->Des());
iReqBodySubmitBuffer = HBufC8::NewMaxL(iPostData->Des().Length() + (iHTTPBoundary.Length()*3) +512);
iReqBodySubmitBufferPtr.Set(iReqBodySubmitBuffer->Des());
iReqBodySubmitBufferPtr.Zero();
iReqBodySubmitBufferPtr.Append(KBoundaryEnd);
iReqBodySubmitBufferPtr.Append(iHTTPBoundary);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(KDisposition);
iReqBodySubmitBufferPtr.Append(KDispFormData);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(KCrLf);
// iReqBodySubmitBufferPtr.AppendNum((int)iOriginalCRC);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(KBoundaryEnd);
iReqBodySubmitBufferPtr.Append(iHTTPBoundary);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(KDisposition);
iReqBodySubmitBufferPtr.Append(KDispUserFile);
iReqBodySubmitBufferPtr.Append(_L8("test.txt"));
iReqBodySubmitBufferPtr.Append(KSingleQuote);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(KMIMETextPlain);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(KCrLf);
iReqBodySubmitBufferPtr.Append(iPostData->Des());
iReqBodySubmitBufferPtr.Append(KBoundaryEnd);
iReqBodySubmitBufferPtr.Append(iHTTPBoundary);
iReqBodySubmitBufferPtr.Append(KBoundaryEnd);
return iReqBodySubmitBufferPtr.Length();
}
TBool CClientEngine::GetNextDataPart(TPtrC8& aDataPart)
{
TBool retVal = EFalse;
TInt err = iReqBodyFile.Read(iPostData->Des());
if (err == KErrNone)
{
aDataPart.Set(iPostData->Des().Ptr());
//++iDataChunkCount;
retVal = (iPostData->Des().Length() == 0);
}
return retVal;
}
Is there anything that I am missing.
Forum posts: 59
There was problem with the GetNextDataPart().
Actually I was transferring data from the file rather than from
iReqBodySubmitBufferPtr.
Thanks a lot.
Any inside into uploading of image stuff.
Forum posts: 19
Can you tell me which Application you are using for the Web Server.also my problem is that i dont know how to set the uri......
For your reference.. am using this .................
url.Copy(_L("http://xxx.x.xx.xxx/MailWebService/Service1.asmx?op=AddStr"));
But the web server is expecting the content of the file which I want to post on the web server as the value of the variable.
Please guide me ...........
Thanx in Advance...
Regards,
Brajesh
Forum posts: 59
I was uploading it to an internal webserver designed by us.
So didnt face any such problem.
Forum posts: 5
I also have to upload mulitple files to some server.
I faced some strange problems there:
1. At first, I performed regular HTTP post with all required headers and in GetNextDataPart() I put the being uploaded file content:
{
 if(iPostData)
 {
// Provide pointer to next chunk of data (return ETrue, if last chunk)
// Usually only one chunk is needed, but sending big file could require
// loading the file in small parts.
aDataPart.Set(iPostData->Des());
 }
return ETrue;
}
void CXXEngine::ReleaseData()
{
 if(iPostData)
 {
delete iPostData;
iPostData = NULL;
}
}
It worked well in case of single upload (i.e. after each file upload some other HTTP GET was performed).
It also worked well on files with different sizes fom 500 bytes till Kb and Mb.
2.After that I had to implement upload sequence of multiple files. Here the problems started.
The process looks like that:
MyAppUi (to make things simple) called some UploadFoo(...) (like well-known IssueHTTPPostL(...) from HTTP example). This function performed all necessary transaction/headers/SubmitL()/etc. stuff. When I received ESucceeded/EFailed I notified AppUi about that and AppUi after 1sec timer called UploadFoo() again to upload next file (in timer thread).
On emulator worked fine, but on the device (N80) sometimes the application was closed. After some research I noticed that after several uploaded files ReleaseData() was not called. And after that on 2nd or 3rd file upload the application simple hang up.
After some additional research I tried to upload many files with less-than-1k length and "voila" - every file was uploaded.
My conclusion was that I had to divide my file content to chunks.
(I still don't understand why it works when i upload only one large file?
I looked at HTTPEXAMPLECLIENT, at very useful posts of Piotr Kundu and reimplemented the GetNextDataPart() and ReleaseData() functions.
Now every time GetNextDataPart(TPtrC8& aDataPart) is called, I put into aDataPart next chunk of file content
{
TBool bRetVal = EFalse;
if(iPostData)Â // whole file buffer - HBufC8*
{
iPostDataChunk->Des().Zero(); // 1K HBufC8* that created in UploadFoo()
TInt nTotalLen = iPostData->Des().Length();
TInt nLastPos = inDataChunkCount*KMaxSubmitSize; // KMaxSubmitSize - 1024
TInt nNewPos = (inDataChunkCount+1)*KMaxSubmitSize;
if(nNewPos > nTotalLen)
{
if(nLastPos > nTotalLen)
{
bRetVal = ETrue;
iPostDataChunk->Des().Copy(_L(""));
}
else
{
iPostDataChunk->Des().Copy( iPostData->Des().Mid(nLastPos) );
inDataChunkCount++;
}
}
else
{
iPostDataChunk->Des().Copy( iPostData->Des().Mid((inDataChunkCount*KMaxSubmitSize), KMaxSubmitSize) );
inDataChunkCount++;
}
aDataPart.Set(iPostDataChunk->Des());
}
else
{
bRetVal = ETrue;
}
// return true if last chunk
return bRetVal;
}
The ReleaseData() function changed also:
{
// Clear out the submit buffer
iPostDataChunk->Des().Zero();
ibReleased = ETrue;
// Notify HTTP of more data available immediately, since it's being read from file
TRAPD(err, iTransaction.NotifyNewRequestBodyPartL());
if (err != KErrNone)
{
_LIT(KNotifyNewRequestBodyPart, "NotifyNewRequestBodyPartL left");
iObserver.StateChanged(eStateUnrecognisedEvent, KNotifyNewRequestBodyPart);
}
}
I received -20Â
The questions are:
- If I divide my file content to chunks, should I implement multipart/form-data upload or I can simple set next chunk of data in GetNextDataPart()?
- I saw also that GetNextDataPart is called twice without ReleaseData() is called - is it ok?
I'm struggling with it couple of days...
I miss something very basic here.....
Any help will be more than appreciated.
Regards,
Genady
Forum posts: 5
As I said, I'm trying to upload multiple files to the server each after another.
When I submit HTTP POST transaction I put some information into custom headers. When server recieve incoming HTTP POST request it can decide to receive or not this file (it can be decided after headers observation).
That was happened in my case. Server received only headers and decided not to accept the file and returned "200 OK" without waiting for HTTP POST request body. It caused crash in some of Symbian HTTP stack related components after 2nd or 3rd subsequent upload.
What I would expect to get is some Error notification in MHFRunError(...) or in MHFRunL(...) callbacks - I receive nothing strange - EGotResponseHeaders, EGotResponseBodyData, EResponseComplete, ESucceeded.
It seems like a Symbian bug.
Regards,
Genady
Forum posts: 3
Any ideas of what I can do to work this out ?
Thanks !!
Forum posts: 59
This panic is raised by the SetActive() member function of an active object, a CActive. It is caused by an attempt to flag the active object as active when it is already active, i.e. a request is still outstanding.
Try pasting some portion of your code and then somebody might be able to help you.