假设您的Java代码正在使用
Apache HttpComponents(那么您应该这样说),当您的代码添加了以下内容时,
URI aWebImageUrl2 = new URI("http://localhost:1337/");
File imgPath = new File("…/face.png");
final String aImgCaption = "face";
HttpClient httpClient = new DefaultHttpClient();
httpClient.execute(post);
提交以下示例HTTP请求(在使用
nc -lp 1337
测试后,参见
GNU Netcat):
POST / HTTP/1.1
Content-Length: 990
Content-Type: multipart/form-data; boundary=oQ-4zTK_UL007ymPgBL2VYESjvFwy4cN8C-F
Host: localhost:1337
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.2 (java 1.5)
--oQ-4zTK_UL007ymPgBL2VYESjvFwy4cN8C-F
Content-Disposition: form-data; name="picture"; filename="face.png"
Content-Type: application/octet-stream
�PNG[…]
在HTML中做这样的事情最简单的解决方案当然是使用一个
FORM
元素,并且不需要或只需要最少的客户端脚本。
<form action="http://service.example/" method="POST"
enctype="multipart/form-data">
<input type="file" name="picture">
<input type="submit">
</form>
它提交以下示例请求(无论是通过提交按钮还是表单对象的
submit()
方法提交)。
POST / HTTP/1.1
Host: localhost:1337
Connection: keep-alive
Content-Length: 886
Cache-Control: max-age=0
Origin: http://localhost
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryhC26St5JdG0WUaCi
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Referer: http://localhost/scripts/test/XMLHTTP/file.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: de-CH,de;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
------WebKitFormBoundaryhC26St5JdG0WUaCi
Content-Disposition: form-data; name="picture"; filename="face.png"
Content-Type: image/png
�PNG[…]
但是,既然您明确要求“javascript”解决方案(实际上并没有
这样的编程语言),我推断您想在提交过程中拥有更多的客户端控制。在这种情况下,您可以使用最近浏览器提供的
W3C文件API和
XMLHttpRequest或
XMLHttpRequest2 API:(注意,这些是浏览器提供的API,而非编程语言):
<script type="text/javascript">
function isHostMethod(obj, property)
{
if (!obj)
{
return false;
}
var t = typeof obj[property];
return (/\bunknown\b/i.test(t) || /\b(object|function)\b/i.test(t) && obj[property]);
}
var global = this;
function handleSubmit(f)
{
if (isHostMethod(global, "XMLHttpRequest"))
{
try
{
var input = f.elements["myfile"];
var file = input.files[0];
var x = new XMLHttpRequest();
x.open("POST", f.action, false);
try
{
var formData = new FormData();
formData.append("picture", file);
x.send(formData);
return false;
}
catch (eFormData)
{
try
{
var reader = new FileReader();
reader.onload = function (evt) {
var boundary = "o" + Math.random();
x.setRequestHeader(
"Content-Type", "multipart/form-data; boundary=" + boundary);
x.send(
"--" + boundary + "\r\n"
+ 'Content-Disposition: form-data; name="picture"; filename="' + file.name + '"\r\n'
+ 'Content-Type: application/octet-stream\r\n\r\n'
+ evt.target.result
+ '\r\n--' + boundary + '--\r\n');
};
reader.readAsBinaryString(file);
return false;
}
catch (eFileReader)
{
}
}
}
catch (eFileOrXHR)
{
}
}
return true;
}
</script>
<form action="http://service.example/" method="POST"
enctype="multipart/form-data"
onsubmit="return handleSubmit(this)">
<input type="file" name="myfile">
<input type="submit">
</form>
这种方法尝试使用XMLHttpRequest API。如果失败,该函数返回
true
,因此将
true
返回给事件处理程序(请参见属性值),并以通常的方式提交表单(后者可能无法与您的Web服务一起使用;在禁用脚本支持之前进行测试)。
如果可以使用XMLHttpRequest,则会“测试”²文件输入是否具有
files
属性,并且所引用的对象是否具有
0
属性(如果支持,引用该表单控件的第一个选择的
File
)。
如果是这样,将尝试使用XMLHttpRequest2 API,该API的
send()
方法
可以接受一个对FormData
的引用并自行完成所有多部分操作。如果不支持XMLHttpRequest2 API(应该会抛出异常),则尝试使用File API的
FileReader
,它可以将
File
的内容读取为二进制字符串(
readAsBinaryString()
);如果成功(
onload
),则准备并提交请求。如果其中一种方法似乎起作用,则不会提交表单(
return false
)。
使用FormData API提交的示例请求:
POST / HTTP/1.1
Host: localhost:1337
Connection: keep-alive
Content-Length: 887
Origin: http://localhost
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLIXsjWnCpVbD8FVA
Accept: */*
Referer: http://localhost/scripts/test/XMLHTTP/file.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: de-CH,de;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
------WebKitFormBoundaryLIXsjWnCpVbD8FVA
Content-Disposition: form-data; name="picture"; filename="face.png"
Content-Type: image/png
�PNG[…]
当使用FileReader API时,示例请求看起来略有不同(只是概念证明)。
POST / HTTP/1.1
Host: localhost:1337
Connection: keep-alive
Content-Length: 1146
Origin: http://localhost
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.202 Safari/535.1
Content-Type: multipart/form-data; boundary=o0.9578036249149591
Accept: */*
Referer: http://localhost/scripts/test/XMLHTTP/file.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: de-CH,de;q=0.8,en-US;q=0.6,en;q=0.4
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
--o0.9578036249149591
Content-Disposition: form-data; name="picture"; filename="face.png"
Content-Type: application/octet-stream
PNG[…]
请注意,XMLHttpRequest2、FormData 和 File API 仅处于“工作草案”状态,因此仍在不断变化。此外,如果提交资源的来源和提交目标资源使用的协议、域名和端口号不同,则您可能需要处理并解决
同源策略问题。根据需要添加功能测试和更多异常处理。
还要注意,使用
FileReader
发送的请求比相同文件更大,并且缺少前导字符,如 Frits van Campen 所提到的问题所示。这可能是由于 (WebKit) 中的错误,您可能想要删除此替代方案;我只需说一句,
readAsBinaryString()
方法已经被 File API 工作草案中的
readAsArrayBuffer()
方法取代,应使用
Typed Arrays。
另请参见
"从 Web 应用程序使用文件"。
1. 使用true
进行异步处理;这样可以避免UI阻塞,但需要在事件监听器中进行处理,并且您始终需要取消表单提交(即使XHR不成功)。
2. 如果无法访问属性,则会抛出异常。如果您更喜欢真实的测试,请实现(额外的)功能测试,并注意并非所有内容都可以安全地进行功能测试。