跨域下载图片走的各种弯路和遇到的种种困难
首先声明 ,我是编程菜鸟,刚做编程不久。欢迎拍砖
要实现的功能:
我们一旅游网站(南北游),可以发表游记。富文本,写攻略,上传图片(我们会对图片进行剪切分成大中小等各种图片,方便不同地方调用)。
但有一种情况是用户会把自己发表在其他网站的文章复制粘贴到我们网站,结果是我们只存下了图片的链接地址,而没有实际保存图片。在其他地方用到该图片的时候就会出现图片大小不合适或者显示挂图的现象。
解决的办法就是根据用户粘贴的地址,将图片下载到我们的服务器上。然后将文本框中其他网站地址换成自己网站的地址,保存。要实现这个功能可算是费了一番周章。
第一次尝试的办法:
在保存之前 使用jquery对富文本框中的图片进行遍历,发现不是我们网站的图片就将其地址发到后台处理,异步提交使用$.post。
大概的一个代码
$("#kecontent>img").each({ var src = $(this).attr("src"); if(src.indexOf("nanbeiyou.com")<=0){ $.post("http://file.nanbeiyou.com/******&remotePath="+encodeURI(src), success:function(){ //do some thing }) ; } });
后台根据传过来的地址,模拟浏览器将图片先保存到内容,然后进行切割保存到服务器上。(很快就实现了,发现要下载的图片保存到服务器上,着实高兴了一下,以为快完工了呢)
改正1
很快发现问题,程序在www.nanbeiyou.com域下,而下载下来的图片在file.nanbeiyou.com资源域下,$.post不能跨域获得返回来的图片名字。负责人说用iframe跨域获得吧。
我想起jquery 的jsonp方式可以跨域,网上查了查:http://www.cnblogs.com/know/archive/2011/10/09/2204005.html ,然后改用$.ajax jsonp进行异步请求。
调试发现使用jsonp确实可以把图片的名字返回来(加上前边固定的地址就ok了),但是当发了多张图片之后,怎么将图片的名字和它原来的地址对应上,替换原来的其他网站地址呢?于是在发送图片原地址之前,给该图片标签属性加上一个唯一标识数(使用index正合适,不用再使用随机数),然后将图片源地址和index数一起发送到后台,后台处理了图片之后,将图片名称和 这个index再返回来,根据返回的index匹配页面中的img,将其中的地址替换。这想法不错,实现之。
前台页面代码:
$("#kecon>img").each(function(index){ var src = $(this).attr("src"); $(this).attr("random",index); if(src.indexOf("nanbeiyou.com")<=0){ $.ajax({ type:"get", async:true, url:"http://file.nanbeiyou.com/*****&remotePath="+encodeURI(src)+"&random="+index, dataType:"jsonp", jsonp: "callbackparam",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(默认为:callback) jsonpCallback:"success_jsonpCallback",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名 success : function(json){ $("#kecon>img[random='"+json[0].random+"']").attr("src")=json[0].name; }, error:function(){ alert('fail'); } }); } });
给<img>加上属性名叫random,后来发现可能这是个jquery保留字,随之将random改成rands。
愚蠢低级的错误总是有:
$("#kecon>img[rands='"+json[0].rands+"']").attr("src")=json[0].name; 应改成
$("#kecon>img[rands='"+json[0].rands+"']").attr("src",json[0].name);
后台实现:这是从网上找的,同事给我的,我也不知道出处。
/// <summary> /// 根据输入的图片网址,将图片下载到内存中 /// </summary> /// <param name="downAddress"></param> /// <returns></returns> private static MemoryStream DownLoad(string downAddress) { MemoryStream ms = new MemoryStream(); HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(downAddress); request.AllowAutoRedirect = false; request.Method = "GET"; request.UserAgent = "Opera/9.25 (Windows NT 6.0; U; en)"; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream responseStream = response.GetResponseStream(); int i; byte[] buffer = new byte[2048]; //delSetProcess pro = new delSetProcess(SetProcess); do { i = responseStream.Read(buffer, 0, 2048); ms.Write(buffer, 0, i); //this.Invoke(pro); } while (i > 0); response.Close(); responseStream.Close(); responseStream.Dispose(); response = null; request = null; responseStream = null; return ms; }
后台保存图片,调用download。
if (context.Request.Params["pictype"] == "Travel" && context.Request.Params["remotePath"] != null) { string oldPath = context.Request.Params["remotePath"]; string oldName = oldPath.Substring(oldPath.LastIndexOf('/')+1); string oldExt = oldPath.Substring(oldPath.LastIndexOf('.')); int index = int.Parse(context.Request.Params["random"]); MemoryStream ms = DownLoad(oldPath); //给保存在本地的图片设定一个随机数名字 string newName = PhotoInfo.GeneratID(); using (PhotoInfoChain pi = ThumbFactory.getInstancePhotoinfoChain("Travel")) { pi.Stream = ms; //根据名字,将图片切成大中小等图片保存到本地服务器 pi.Save(newName);
newExt = pi.InfoList[0].ImgFormat; context.Response.ContentType = "text/plain"; string callbackFunName = context.Request["callbackparam"]; context.Response.Write(callbackFunName + "([{name:\""+newName + newExt+"\",rands:\""+index+"\"}])"); }; }
改正2
使用上边的jquery方法,当往文本框粘贴了两张图片时,打开浏览器调试查看网络信息,是往后台发送了两个jsonp异步请求,每个请求都成功返回了获得了图片新名称和index (0和1),但是$.ajax()中的success方法中得到的json,能得到两次,但是两次的json[0].rands都是1,或者都是0.而不是两个都获得。也不知道是什么原因。哪位高手遇到过,给解释解释。
还有就是使用异步,将多个请求都发出去了,不知道什么时间图片都下载完成,地址替换完成,什么时间将整个游记表单提交保存。所以放弃一个图片地址一个jsonp请求。而是首先对页面文本框进行遍历,将需要替换的所有图片地址放到一个string变量中,用逗号分隔。一次异步请求所有的图片地址发送到后台代码,然后后台代码对发送的来的图片地址string对象解析分隔,对每个地址进行图片下载,然后将图片新名字和图片原地址(跟前台页面中的地址匹配,以便付新值替换)存入json中返回。
改进3
试着以上方法粘贴了10张左右图片,可以成功保存游记,这个过程有点慢,但是能成功,所以在点击保存游记的同时弹出一个小提示“游记正在保存,请稍后。。”原理很简单,有个隐藏div,点击保存之后,该提示div显示出来。
改进4
使用改进3之后,多张图片也保存了,也给用户一个提醒了,原以为这样就ok了。没想到又发现问题了,当粘贴的图片很多时比如50张图片,保存不成功了,一直提示正在保存,也不报错,发出去的jsonp请求没有了回音,没有查看浏览器网络信息,就以为是发送时间之后时间太长,超时了,所以不成功。查查百度、谷歌。$.ajax确实有个timeout参数,更认为是timeout原因。所以给$.ajax设置上timeout参数之后,还是保存失败,也没有任何提醒。 心想保存失败就失败吧,怎么也要给个提示告诉我失败了,别让我等着了,设定的error函数怎么不触发呢?
原来对于jsonp格式的$.ajax请求,对于jquery1.5版本之前,error方法不能触发。我们就用的jquery1.4,它怎么就这么寸呢。还好看有个插件jquery.jsonp解决了这个问题,好吧再使用jquery.jsonp方法再重写一遍吧。 jquery.jsonp api :https://github.com/jaubourg/jquery-jsonp/blob/master/doc/API.md.
这种的默认回调函数名为:“_jqjsp”。改写好之后,设置timeout短一些之后,还真能检测到timeout错误。心想这总成了吧,把timeout稍微设置长一点,一次粘贴40多张图片,点击保存。报错error错误,不是timeout错误。这是什么原因呢?查看一些浏览器 网络监测。http 400错误,url过长。当初使用$.ajax时就保存失败也不报错,是不是也是url过长的原因呢。url过长能不能用post提交呢,尝试了一把 $.ajax jsonp格式的提交只能用“get”,不可以用"post"提交。完了做的工作都白做了,一切都归零了。
改用iframe提交吧,iframe可以使用post提交,这样提交的个数就没有限制了。
后台图片保存程序改用多线程保存。对需要下载的图片放到一个dictionary{picpath,‘等待’}中。用for循环遍历,下载每一个图片。由于使用多线程,所以在一个线程判断dictionary中该图片是否下载时,进行锁定。修改该图片的状态“等待”到‘正在下载’。然后放开锁。其他线程就可以判断了,如果状态是“正在下载”,就continue,循环判断下个图片是否下载了。对于用于图片保存分隔的类pi,由于多线程共用,会导致一个图片还没下载完保存,就有新图片加入,导致下载下来的图片不完整。所以把这种共享类放到每个线程中,给每个线程单独分(实例化)一个。在子线程保存的时候根据相对路径判断物理路径是使用了:httpcontent.current.server.mappath()方法,而在子线程中httpcontent.current为null无法判断。后来在传递参数的时候直接传物理地址,而是相对地址。这个转换在主线程中可以完成。 如果用户往富文本中粘贴两边重复的图片,会导致dictionary中键值重复,随在前天页面判断是否有重复的图片地址,如果存在则只在source中存入一份传到后台。当传递了两张重复的图片地址,在返回来替换的时候,如果只是replace,则只会替换一个,第二个符合条件的不进行替换。使用正则表达式则可以解决这个问题。
后台下载保存可能会出现异常,捕获异常,然后返回图片名字为“nanbeiyou”,其他接到反馈进行判断,如果是“nanbeiyou”就知道该图片下载失败,不替换该图片地址。
前天往后台传递用post没有了图片数量限制,但是在后台跳转的时候url长度还是有的限制,随在前台对富文本中图片的数量进行判断,每90张往后台传一次,返回回调,再回调中判断未处理的图片个数,如果为0,就提交整个表单,如果大于90张,则传90张,在0到90之间,则是吧剩余的都传完。
后发现有些网站上的图片url很特殊:以qq空间为例:http://user.qzone.qq.com/307722709/blog/1341022967#!app=2&via=QZ.HashRefresh&pos=1340365654中的一个图片地址:
http://b206.photo.store.qq.com/http_imgload.cgi?/rurl4_b=809f8ca26fba5d947e8642f699ec5571421e5e9c7e38a6cbfdf982c0696537766a8ab54a8f9302ed2743d23408c344b9e9dfeea2928c4f3e0b25cb74c9ebb0da168db30206c32443959287af61818a8321d7dbf5&a=206&b=206
1、该图片地址中没有图片后缀名,通过url分析得不到图片类型。所以在通过reque请求之后,得到
HttpWebResponse response = (HttpWebResponse)request.GetResponse()
之后,可以通过response.contentType获得图片的类型,从而得到后缀名。
2.地址中&粘贴到富文本框中之后,对应的隐藏域Tcontent中时&所以需要对该url进行正则匹配将所以的&换成&
3、有的图片地址下载时需要跳转类似于mvc这种,所以将download()中的
request.AllowAutoRedirect = false;这一句去掉。
4、url中存在一个?,在正则中这是一个正则匹配符合,所以需要将该url中?换成\? 在才能正则匹配到正确的url
4、
// $.ajax({
// type:"get",
// timeout:10000,
// async:true,
// url:"@(filePath)/upload.upx?random="+Math.random()+"&pictype=Travel&remotePath="+encodeURI(sources),
// dataType:"jsonp",
// jsonp: "callbackparam",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(默认为:callback)
// jsonpCallback:"success_jsonpCallback",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名
// success : function(json){
// $("#Cover").val(json[0].newPath);
// for(var i=0;i<json.length;i++){
// //$("#kecontext>img[src='"+json[i].oldPath+"']").attr("src",$("#imgpath").val()+json[i].newPath);
// if ($("#imgTitle").val() == "") {
// $("#imgTitle").val(json[i].newPath);
// }
// else {
// $("#imgTitle").val($("#imgTitle").val() + ";" + json[i].newPath);
// }
// $("#Tcontent").val($("#Tcontent").val().replace(json[i].oldPath,$("#imgpath").val()+json[i].newPath));
//
// }
//
// $("#form").submit();
//
// },
// error:function(x,t,m){
// if(t==="timeout")
// {
// alert("您上传的图片太多,保存失败!");
// }else{
// alert("t="+t);
// alert("m="+m);
// alert("x="+x);
// }
//
// }
// });
// $.jsonp({
// type:"post",
// url:"@(filePath)/upload.upx?random="+Math.random()+"&pictype=Travel&remotePath="+encodeURI(sources)+"&callback=?",
// timeout:120000,
// context:sources,
// //callbackParameter:"callbackparam=success_jsonpCallback",
// success:function(json,textStatus){
// $("#Cover").val(json[0].newPath);
// for(var i=0;i<json.length;i++){
//
// if ($("#imgTitle").val() == "") {
// $("#imgTitle").val(json[i].newPath);
// }
// else {
// $("#imgTitle").val($("#imgTitle").val() + ";" + json[i].newPath);
// }
// $("#Tcontent").val($("#Tcontent").val().replace(json[i].oldPath,$("#imgpath").val()+json[i].newPath));
//
// }
// $("#form").submit();
//
// },
// error:function(xOptions, textStatus){
// if(textStatus==="timeout"){
// alert("游记保存超时");
// }
// else{
// alert("发生错误了!");
// }
//
// }
//
// });
string returnVal = "[";
string returnVal = "";
foreach (string path in arr)
{
string oldPath = path;
string oldName = oldPath.Substring(oldPath.LastIndexOf('/') + 1);
string oldExt = oldPath.Substring(oldPath.LastIndexOf('.'));
MemoryStream ms = DownLoad(oldPath);
string newName = PhotoInfo.GeneratID();
string newExt = "";
Func<string, string> _func = x => x;
using (PhotoInfoChain pi = ThumbFactory.getInstancePhotoinfoChain("Travel"))
{
pi.Func = _func;
pi.Filename = oldName;
pi.Stream = ms;
pi.Save(newName);
ms.Dispose();
newExt = pi.InfoList[0].ImgFormat;
//returnVal +="{oldPath:\""+ oldPath +"\"" +","+ "newPath:" +"\""+ newName + newExt+"\"},";
returnVal += newName + newExt + ":";
}
}
returnVal = returnVal.Substring(0, returnVal.Length - 1);
returnVal += "]";
string callbackFunName = context.Request["callbackparam"];
context.Response.Write(callbackFunName + "(" + returnVal + ")");
context.Response.Write("_jqjsp" + "(" + returnVal + ")");