2007-08-15

通过 HTTP POST 发送二进制数据

Views: 37265 | Add Comments

我暂时还不清楚 HTTP 文件上传的具体细节, 但是我知道通过浏览器上传文件, 然后用 PHP 接收, 需要使用 PHP 的预定义变量 $_FILES. 最近我有一个应用需要使用 PHP 的预定义变量 $_POST 获取上传的文件(或者任何数据), 也就是通过 HTTP POST 参数传递二进制数据.

工作在 ContentType = "application/x-www-form-urlencoded" 的模式下时, HTTP 协议使用 ASCII 字符集的一个子集来编码要传输的字节流. 如字符串 "a@" 如果以 ASCII 编码存在于内存中(以字节数组的形式), 也就是内存按字节读取为 HEX 是: 0x61 0x40, 那么该字节数组被传输时会被编码(URL 编码)成为 0x61 0x25 0x34 0x30, 被编码后字节数组如果按照 ASCII 编码显示, 就是 "a%40". 也就是说, 当你想传输 0x61 0x40 时, HTTP 传输的是 0x61 0x25 0x34 0x30.

这种编码过程几乎所有的编程语言都提供支持, 在 C# 中你可以使用 HttpUtility.UrlEncode(), 输入一个字节数组, 返回一个 URL 编码的 字节数组.

在 PHP 脚本中, 当你使用 $_POST['data'] 获取数组时, 该数组已经被 PHP 自动解码了. 按照上面的例子, $_POST['data'] 所指向的内存内容为 0x61 0x40 -- 但是, 默认情况下并不总是, 如果该数据包含单引号等少数几个字符, 那么它们的前面会被 PHP 加上 \.

下面先给出 PHP 脚本, 保存一个通过 HTTP 参数传递的文件:

<?php
    $filename = $_POST['filename'];
    $data = $_POST['data'];
    if(get_magic_quotes_gpc()){
        // 去掉斜杠
        $data = stripslashes($data);
    }
    file_put_contents($filename, $data);
?>

C# 客户端为:

using System;
using System.Text;
using System.Net;
using System.Web;
using System.IO;

class Test
{
    public static void Main(){
        try{
            WebResponse response;
            HttpWebRequest request;
            request = (HttpWebRequest)HttpWebRequest.Create("http://localhost/test.php");
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";

            String filename = "test.jpg";
            // HTTP 参数名
            byte[] keyBytes = Encoding.UTF8.GetBytes(String.Format("filename={0}&data=", filename));

            FileStream fs = new FileStream(filename, FileMode.Open);
            byte[] buffer = new byte[8192];
            int n = fs.Read(buffer, 0, buffer.Length);
            byte[] dataArray = new byte[n];
            Array.Copy(buffer, dataArray, n);
            dataArray = HttpUtility.UrlEncodeToBytes(dataArray);

            request.ContentLength = keyBytes.Length + dataArray.Length;

            Stream dataStream = request.GetRequestStream();
            // 发送参数名
            dataStream.Write(keyBytes, 0, keyBytes.Length);
            // 发送名为 data 对应的值.
            dataStream.Write(dataArray, 0, dataArray.Length);
            dataStream.Close();

            response = (HttpWebResponse)request.GetResponse();
            // 读取服务器的返回, 在本应用中, 如果正常, 返回空字符串.
            StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding("UTF-8"));
            Console.Write(sr.ReadToEnd());

            response.Close();
        } catch (Exception e) {
            Console.Write("Error: " + e.ToString() + "\r\n");
        }
    }

}

如果你要测试的文件小于 8K, 可以直接使用上面的程序测试. 如果大于, 你可以加大 buffer 的容量.

上传成功后, 使用文件比较工具对比两个文件是否一致:

diff test.jpg up_test.jpg

这种方式的典型的应用是自定义文件上传, 你只需要更改少量的代码就可以实现支持断点续传功能的文件上传.

Related posts:

  1. HTTP POST using PHP cURL
  2. 用脚本语言开发网游 – C整合Python
  3. PHP 用 curl 读取 HTTP chunked 数据
  4. C# 中实现 FIFO 缓冲区–ArrayBuffer
  5. iOS 正确接收 HTTP chunked 数据的方法
Posted by ideawu at 2007-08-15 11:21:02

Leave a Comment