2012年12月17日 星期一

Sending a file and some form data via HTTP post in C#


A few weeks back, I wrote some logic to send a file and a from a windows client over to a Java server endpoint. There are a few ways to do that: via application protocols like FTP and HTTP or even implementing a custom protocol using TCP/IP. Since the Java server was already serving HTTP requests and that HTTP requests can usually get through firewalls quite easily, I chose the HTTP protocol.

Figuring out the HTTP request

The first step to writing the logic was simple -- to see how browsers form a HTTP post request. With that, I started by constructing the following html form:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<html>
<body>
<form action="http://ipv4.fiddler/test/GetPostRequest.php"
enctype="multipart/form-data"
method="post">
<p>
<strong>My file description:</strong>
<textarea name="myFileDescription" rows="2" cols="20">
</textarea><br/> <br/>
<strong>My file:</strong><br/>
<input type="file" name="myFile">
</p>
<input type="submit" value = "Submit">
</form>
</body>
</html>
And using Fiddler, I was able to get the following HTTP post request that was sent by my browser after I submitted a form input.
POST http://127.0.0.1/GetPostRequest.php HTTP/1.1 Host: 127.0.0.1 Connection: keep-alive Referer: http://localhost/GetPostRequest.php Content-Length: 1611568 Cache-Control: max-age=0 Origin: http://localhost User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.91 Safari/534.30 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryX6nBO7q27yQ1JNbb Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip,deflate,sdch Accept-Language: en-US,en;q=0.8 Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3 ------WebKitFormBoundaryX6nBO7q27yQ1JNbb Content-Disposition: form-data; name="myFileDescription" My sample file description. ------WebKitFormBoundaryX6nBO7q27yQ1JNbb Content-Disposition: form-data; name="myFile"; filename="SomeRandomFile.pdf" Content-Type: application/pdf file contents... ------WebKitFormBoundaryX6nBO7q27yQ1JNbb--

Constructing the HTTP post request in C#

With the HTTP request constructed by my browser, constructing the HTTP post request in C# became very straightforward. There are classes from the System.Net and System.IO libraries that can be used for the generating and sending the request over to the server endpoint. The following is a proof of concept that I did for my actual solution:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// Create a http request to the server endpoint that will pick up the
// file and file description.
HttpWebRequest requestToServerEndpoint =
    (HttpWebRequest)WebRequest.Create("http://localhost/GetPostRequest.php");
 
string boundaryString = "----SomeRandomText";
string fileUrl = @"C:\SomeRandomFile.pdf";
 
// Set the http request header \\
requestToServerEndpoint.Method = WebRequestMethods.Http.Post;
requestToServerEndpoint.ContentType = "multipart/form-data; boundary=" + boundaryString;
requestToServerEndpoint.KeepAlive = true;
requestToServerEndpoint.Credentials = System.Net.CredentialCache.DefaultCredentials;
 
// Use a MemoryStream to form the post data request,
// so that we can get the content-length attribute.
MemoryStream postDataStream = new MemoryStream();
StreamWriter postDataWriter = new StreamWriter(postDataStream);
 
// Include value from the myFileDescription text area in the post data
postDataWriter.Write("\r\n--" + boundaryString + "\r\n");
postDataWriter.Write("Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}",
                        "myFileDescription",
                        "A sample file description");
 
// Include the file in the post data
postDataWriter.Write("\r\n--" + boundaryString + "\r\n");
postDataWriter.Write("Content-Disposition: form-data;"
                        + "name=\"{0}\";"
                        + "filename=\"{1}\""
                        + "\r\nContent-Type: {2}\r\n\r\n",
                        "myFile",
                        Path.GetFileName(fileUrl),
                        Path.GetExtension(fileUrl));
postDataWriter.Flush();
 
// Read the file
FileStream fileStream = new FileStream(fileUrl, FileMode.Open, FileAccess.Read);
byte[] buffer = new byte[1024];
int bytesRead = 0;
while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
{
    postDataStream.Write(buffer, 0, bytesRead);
}
fileStream.Close();
 
postDataWriter.Write("\r\n--" + boundaryString + "--\r\n");
postDataWriter.Flush();
 
// Set the http request body content length
requestToServerEndpoint.ContentLength = postDataStream.Length;
 
// Dump the post data from the memory stream to the request stream
using (Stream s = requestToServerEndpoint.GetRequestStream())
{
    postDataStream.WriteTo(s);
}
postDataStream.Close();