欢迎光临
我们一直在努力

ASP无组件上传·从原理剖析到实践(上)-ASP教程,ASP应用

建站超值云服务器,限时71元/月

无组件上传一直是困扰大家的一个问题。其实原理很简单,核心就是分析字符串。但是,实际操作时,却困难重重。其中的关键问题还是大家往往对原理的剖析不够深入,或者是因为过程过于繁琐,导致bug不断。一直以来,都想做一个完善的例子,只不过想想就头痛,加上没时间(借口,呵呵 ),所以没有付诸行动。

今天就咬咬牙,给大家提供一个完整的无组件上传的例子。因为本人耐性不好,所以咱们一点一点来,分几天完成。未来的几天,我会天天更新这个文档,这个过程也是大家学习和提高的过程。

(完整的源码和示例,可以在这里找到:http://www.2yup.com/asp/attach/a0000006.zip)

==============================================================

第一天:认识我们的解剖对象——数据

上传文件时,首先要知道我们得到的是什么。下面是一个上传文件的表单,我们就从他开始。

<form action="doupload.asp" method=post enctype="multipart/form-data">

file1说明:<input type=text name=file1_desc> &nbsp;

file1<input type=file name=file1><br>

file2说明:<input type=text name=file2_desc> &nbsp;

file2<input type=file name=file2><br>

<input type=submit name=upload value=upload>

</form>

表单中enctype="multipart/form-data"的意思,是设置表单的mime编码。默认情况,这个编码格式是application/x-www-form-urlencoded,不能用于文件上传;只有使用了multipart/form-data,才能完整的传递文件数据,进行下面的操作(有兴趣的朋友,可以自己试试看两者的异同。方法很简单,就是把这一句去掉)。现在,我们在表单中分别填入数据:

file1的说明 d:\我的 图片\back046.gif

file2的说明 d:\我的 图片\back293.gif

这里用了中英文、空格混排。目的是让例子更有一般性。我选的这两个图片分别是54和62字节。大图片的原理完全一样,不过小图片做例子更合适些,原理容易展现。

为了看到我们得到的数据,在doupload.asp里,有这几行代码:

<%

formsize=request.totalbytes

formdata=request.binaryread(formsize)

response.binarywrite(formdata)

%>

很简单,作用就是打出来传过来的所有数据。如果不熟悉,你可以先研究一下request和response对象的这两个方法。

提交表单,我们在ie里面查看html源,得到:

—————————–7d22131090458

content-disposition: form-data; name="file1_desc"

file1μ??μ?÷

—————————–7d22131090458

content-disposition: form-data; name="file1"; filename="d:\?òμ? í???\back046.gif"

content-type: image/gif

gif89a‘ì?f?f3?ì???ì!ù,@?.á?o ;

—————————–7d22131090458

content-disposition: form-data; name="file2_desc"

file2μ??μ?÷

—————————–7d22131090458

content-disposition: form-data; name="file2"; filename="d:\?òμ? í???\back293.gif"

content-type: image/gif

gif89a(‘???yyyììì!ù,(@l&#8364;?j(·"j?n(34ˉ;

—————————–7d22131090458

content-disposition: form-data; name="upload"

upload

—————————–7d22131090458–

不用怀疑,这就是你从上一个“简单”表单传过来的东西。现在想想看,怎么对付这一堆东西?是不是看上去有规律,又不知道从何下手?明天,咱们就分析一下这堆“图片”,看看怎么分离出我们要的内容。

==============================================================

第二天:分拆初步

睡了个好觉,大家脑子清醒多了吧?今天中午吃的火锅,阿森纳vs.铁哥也没看完,现在一脑子大油。。。

ok,咱们继续研究这个枯燥的问题。首先,要找出规律。看上去似乎很简单,就是用

—————————–7d22131090458

做分隔,这样,每一个文本单元里,都是

content-disposition: form-data; name="表单域的名字";

表单域的内容

而每一个文件单元里,都是

content-disposition: form-data; name="表单域的名字"; filename="文件全路径"

content-type: 文件类型

文件的二进制内容

那么,是不是直接用

split(formdata,"—————————–7d22131090458")

就可以得到各个单元了呢?答案是否定的。首先,formdata不是字符串而是二进制串,不能用split的方法;其次,这里的7d22131090458并不固定,每次都会有变化,并不适合做分隔符。所以,应该用一个更保险的办法。想到没?很简单——就用formdata的第一行做分隔符。只要用instrb函数得到换行符的位置,然后用leftb或midb函数截取数据就行了。我们动手试试:

<%

二进制的回车<return>

bncrlf=chrb(13) & chrb(10)

得到formdata

formsize=request.totalbytes

formdata=request.binaryread(formsize)

得到分隔符

divider=leftb(formdata,clng(instrb(formdata,bncrlf))-1)

看看对不对?

response.binarywrite(divider)

%>

运行。。。成功了!得到了需要的divider。注意,这里的字符串函数都是针对二进制数据操作的,所以,用的是他们的二进制版,加了“b”(binary的首字母)——instrb,leftb(以后可能还出现rightb,midb,lenb。。等等)。毕竟,formdata是用“binaryread()”得到的嘛。好了,有的分隔符,就可以得到数据了。我们从简单的开始,先拿第一个单元出来看看,目标是得到表单域名称和数据。

<%

这是回车<return>

bncrlf=chrb(13) & chrb(10)

得到数据

formsize=request.totalbytes

formdata=request.binaryread(formsize)

得到divider,分隔符

divider=leftb(formdata,clng(instrb(formdata,bncrlf))-1)

起始位置

startpos = instrb(formdata,divider)+lenb(divider)+lenb(bncrlf)

终止位置,从起始位置开始到下一个divider

endpos = instrb(startpos, formdata, divider)-lenb(bncrlf)

part1 = midb(formdata, startpos, endpos-startpos)

response.binarywrite(part1)

%>

这一段有注释,相信大家没问题。如果对这些函数不了解,可以到http://www.2yup.com/asp/referrence/index.asp下载msdn参考看看vbscript的函数用法,对提高水平有很大帮助。

这时候得到的结果可以通过查看生成的html源的方式看到:

content-disposition: form-data; name="file1_desc"

file1的说明

好了,离成功又进一步!

下来只要分别读取part1里name="和第一个“双引号+回车”之间的内容就可以得到表单域的名称;读取连续两个回车之后的内容就可以得到表单域的值了。下面一段顺理成章:

<%

这就是name="

const_nameis=chrb(110)&chrb(97)&chrb(109)&chrb(101)&chrb(61)&chrb(34)

这是回车<return>

bncrlf=chrb(13) & chrb(10)

得到数据

formsize=request.totalbytes

formdata=request.binaryread(formsize)

得到divider,分隔符

divider=leftb(formdata,clng(instrb(formdata,bncrlf))-1)

起始位置

startpos = instrb(formdata,divider)+lenb(divider)+lenb(bncrlf)

终止位置,从起始位置开始到下一个divider

endpos = instrb(startpos, formdata, divider)-lenb(bncrlf)

part1 = midb(formdata, startpos, endpos-startpos)

得到表单域名称,就是<input type=sometype name=somename>里的somename。

fldname = midb(part1,_

instrb(part1, const_nameis)+lenb(const_nameis),_

instrb(part1, bncrlf)-instrb(part1,const_nameis)-lenb(const_nameis)-1)

得到表单域的值

fldvalue = midb(part1,_

instrb(part1, bncrlf&bncrlf)+lenb(bncrlf&bncrlf),_

lenb(part1)-instrb(part1, bncrlf&bncrlf)+lenb(bncrlf&bncrlf))

检查一下?可以每次打开一个注释,分别检查。

response.binarywrite(fldname)

response.binarywrite(fldvalue)

%>

执行一下?呵呵,没问题啦,分别打开注释,会在ie里看到“file1_desc”和“file1的说明”。

当然,这是得到文本单元的方法,不过看看上边的原始数据就知道,得到文件单元方法可以说是基本相同,只不过:

1。需要额外得到filename=""里的值,也就是文件全路径;

2。需要额外得到content-type: 后边的值,也就是文件的类型。

这个工作就是体力劳动了,相信大家没问题。现在更大的精力应该放在:怎么得到所有的段落内容?想来应该是某种形式的循环,但是,具体怎么做?还有,怎么样组织得到的东西,才不显得凌乱?

呵呵,不早了,这个就是咱今天晚上要做的梦了。明天来,咱就一起解决这个问题。。。。

==============================================================

第三天:得到所有的文本单元

wake up!继续啦~~~~~

昨天,我们已经找到了得到一个单元的信息的办法,不过,还没有到实用阶段。毕竟,想实用,还至少要:

对于文本单元,能按名称检索的内容;

对于文件单元,能按名称得到文件的具体内容、类型、全路径、以及大小等信息。

今天,我们就首先着手解决文本单元的问题。

得到内容也许不难,可是,怎么组织才能使这个过程井井有条,才能符合我们的一般习惯?我们可以从现有的知识里找答案。

大家都知道,asp有一个内置对象request,他的功能是得到用户请求的相关信息,包括表单域的值。粗看上去,他的form集合的用法和我们要实现的得到文本单元内容的功能是很近似的,我们来看看request.form的几个应用的例子:

得到表单域的值 –

request.form("表单域名称")或request.form("表单域在<form></form>里的序号")

得到同名表单域的各个元素 –

request.form("表单域名称")(i)或request.form("表单域在<form></form>里的序号")(i)

得到同名表单域的个数 –

request.form("表单域名称").count或request.form("表单域在<form></form>里的序号").count

如果我们能够用ourrequest.form("name"),ourrequest.form(index),ourrequest.form("name").count,ourrequest.form(index).count这样的方式,或是与之相近的方式,不就可以很好的和request对象对应起来么?而且,因为对request对象本身的熟悉,也会降低使用我们自己方法的时候的门槛,相对于写一堆getvalue(name)函数这样的方法,更不容易出错,扩展性更好更灵活,可读性也好得多。那么,我们就看看如果要实现自己的request对象,都有哪些工作要做。

首先,ourrequest应该是一个对象,有自己的属性和方法。只有这样,才可能和现有的request对象做呼应。在vbs5里面,已经可以通过class关键字,来实现自己的类了,所以,可行性上是没有问题的,只要我们自己定义一个类,然后实例化他,就可以得到我们所需的对象;

其次,因为ourrequest.form可以用名称和序号检索,所以,应该提供比较丰富的访问方式;

第三,在表单里有多个域名称相同的时候(比如多个checkbox),应该能够得到其中的各个元素,并且可以得到总个数。所以,ourrequest.form()得到的,应该也是一个可以检索的对象,而且有count属性。

最终,结合vbscript的语言特点,兼顾开发效率,我们决定实现这样的几个类:

a。uploadrequest

这个类和request对象是对应的

属性:

rawdata 得到原始数据,方便检查[只读]

forms 得到一个有count属性的计数器,

可以用outrequest.forms.count的方式,得到文本表单域的的个数[只读]

form(index) 可以用数字或文本检索文本表单域,做用类似request.form。

他返回一个formelement型的对象

b。formelement

可以把它看成单个表单域的化身。通过这个类,可以得到详细的表单域信息,比如name,value等等。如果有多个value(比如checkbox的情况),还可以选择用序号索引

属性:

value 得到表单域的值。如果有多个(比如checkbox),

返回所有的,用逗号分隔[默认]

name 得到表单域的名称

item(index) 用数字索引多个值中的某一个

count 得到对应一个name,所拥有的value的个数。主要用于checkbox[只读]

c。counter

一个辅助类,就是为了实现outrequest.forms.count功能。这里写的并不好,不过考虑大家的理解方便,先暂时这样。

属性:

count 得到count

方法:

setcount 设置count

下面,我们就来看看这几个类的实现:

<%

class formelement

m_开头,表示类成员变量。

private m_dicitems

private sub class_initialize()

set m_dicitems = server.createobject("scripting.dictionary")

end sub

count是咱们这个类的一个只读属性

public property get count()

count = m_dicitems.count

end property

value是一个默认属性。目的是得到值

public default property get value()

value = item("")

end property

name是得到文本域名称。就是<input name=xxx>里的xxx

public property get name()

keys = m_dicitems.keys

name = keys(0)

name = left(name,instrrev(name,"_")-1)

end property

item属性用来得到重名表单域(比如checkbox)的某一个值

public property get item(index)

if isnumeric(index) then 是数字,合法!

if index > m_dicitems.count then

err.raise 1,"indexoutofbound", "表单元素子集索引越界"

end if

itms = m_dicitems.items

item = itms(index)

elseif index = "" then 没给值?那就返回所有的!逗号分隔

itms = m_dicitems.items

for i = 0 to m_dicitems.count-1

if i = 0 then

item = itms(0)

else

item = item & "," & itms(i)

end if

next

else 给个一个不是数字的东东?出错!

err.raise 2,"illegalargument", "非法的表单元素子集索引"

end if

end property

public sub add(key, item)

m_dicitems.add key, item

end sub

end class

class uploadrequest

private m_dicforms

private m_bformdata

private sub class_initialize()

set m_dicforms = server.createobject("scripting.dictionary")

call fill()

end sub

有了这个,就可以检查原始数据了

public property get rawdata()

rawdata = m_bformdata

end property

这一段丑陋的代码是为了实现outrequest.forms.count这个功能。

public property get forms()

set forms = new counter

forms.setcount(m_dicforms.count)

end property

public property get form(index)

if isnumeric(index) then 是数字?用数字来检索

if index > m_dicforms.count then

err.raise 1,"indexoutofbound", "表单元素索引越界"

end if

items = m_dicforms.items

set form = items(index)

elseif vartype(index) = 8 then 字符串?也行!

if m_dicforms.exists(index) then 存在,就返回值

set form = m_dicforms.item(index)

else 不存在,就给个空值——request对象就是这么做的。

exit property

end if

else 给了一个不是数字也不是字符串的东东?出错!

err.raise 2,"illegalargument", "非法的表单元素索引"

end if

end property

private sub fill

得到数据

m_bformdata=request.binaryread(request.totalbytes)

调用这个函数实现递归循环,读取文本单元

call filleveryfirstpart(m_bformdata)

end sub

private sub filleveryfirstpart(data)

这就是name="

const_nameis=chrb(110)&chrb(97)&chrb(109)&chrb(101)&chrb(61)&chrb(34)

这就是filename="

const_filenameis=chrb(102)&chrb(105)&chrb(108)&chrb(101)&_

chrb(110)&chrb(97)&chrb(109)&chrb(101)&chrb(61)&chrb(34)

这是回车<return>

bncrlf=chrb(13) & chrb(10)

得到divider,分隔符

divider=leftb(data,instrb(data,bncrlf)-1)

起始位置

startpos = instrb(data,divider)+lenb(divider)+lenb(bncrlf)

终止位置,从起始位置开始到下一个divider

endpos = instrb(startpos, data, divider)-lenb(bncrlf)

if endpos < 1 then 没有下一个了!结束!

exit sub

end if

part1 = midb(data, startpos, endpos-startpos)

得到part1的第一行

firstline = midb(part1, 1, instrb(part1, bncrlf)-1)

没有filename=",有name=",说明是一个文本单元(这里有一个bug,自己研究一下?当作业吧)

if not instrb(firstline, const_filenameis) > 0_

and instrb(firstline, const_nameis) > 0 then

得到表单域名称,就是<input type=sometype name=somename>里的somename。

fldname = b2s(midb(part1,_

instrb(part1, const_nameis)+lenb(const_nameis),_

instrb(part1, bncrlf)_

-instrb(part1, const_nameis)-lenb(const_nameis)-1))

得到表单域的值

fldvalue = b2s(midb(part1,_

instrb(part1, bncrlf&bncrlf)+lenb(bncrlf&bncrlf),_

lenb(part1)-instrb(part1, bncrlf&bncrlf)+lenb(bncrlf&bncrlf)))

if m_dicforms.exists(fldname) then

set felement = m_dicforms.item(fldname)

m_dicforms.remove fldname

else

set felement = new formelement

end if

felement.add fldname&"_"&felement.count, fldvalue

m_dicforms.add fldname, felement

end if

截取剩下的部分,递归调用这个函数,来得到下一个part1。

call filleveryfirstpart(rightb(data, lenb(data)-endpos-1))

end sub

这是一个公用函数,作用是二进制和字符串的转换

private function b2s(bstr)

if not isnull(bstr) then

for i = 0 to lenb(bstr) – 1

bchr = midb(bstr,i+1,1)

if ascb(bchr) > 127 then 遇到了双字节,就得两个字符一起处理

temp = temp & chr(ascw(midb(bstr, i+2, 1) & bchr))

i = i+1

else

temp = temp & chr(ascb(bchr))

end if

next

end if

b2s = temp

end function

end class

这是一个辅助类,为了实现outrequest.forms.count功能。

class counter

private m_icnt

count是咱们这个类的一个只读属性

public property get count()

count = m_icnt

end property

public function setcount(cnt)

m_icnt = cnt

end function

end class

%>

<%

下面是测试码

set outrequest = new uploadrequest

%>

<%=outrequest.form(0).name%>:<%=outrequest.form("file1_desc")%><br>

<%=outrequest.form(1).name%>:<%=outrequest.form("file2_desc")%><br>

<%=outrequest.form(2).name%>:<%=outrequest.form(2).count%><br>

<%=outrequest.form(3).name%>:<%=outrequest.form(3)%><hr>

一共有<%=outrequest.forms.count%>个文本单元

这里的注释很详细,而且,每一个类的属性和方法都很少,所以相信基础好的朋友读懂是没有问题的。对应的,我们的测试表单也改成了:

<form action="doupload.asp" method=post enctype="multipart/form-data">

file1说明:<input type=text name=file1_desc> &nbsp;

file1<input type=file name=file1><br>

file2说明:<input type=text name=file2_desc> &nbsp;

file2<input type=file name=file2><br>

<input type=checkbox name=chk value=a>a

<input type=checkbox name=chk value=b>b

<input type=checkbox name=chk value=c>c

<input type=checkbox name=chk value=d>d

<input type=checkbox name=chk value=e>e<hr>

<input type=submit name=upload value=upload>

</form>

注意,这里的每一个文本表单域都要填上,因为测试码给得很特殊,读了0,1,2,3各个项目的值,测试了各个属性。不过,现实情况下,因为事先知道表单域的名称;即使不知道,也可以用outrequest.forms.count来循环读取,所以是没问题的,不容易出错。

现在,试试看!怎么样?成功了吧 呵呵,中英文都没有问题,用法也很简单,很清晰。现在,我们就可以说基本上解决了文本域的读取问题。

——————————————————–

今天这一段是很有挑战性的。我写了两个多小时。对于尚处于初级的朋友,可能会觉得有些吃力。其实,关键在于深刻的理解类的概念,如果这一点没有问题,那么,理解这些代码就不在话下了。对了,今天的代码里有一个比较明显的bug(我故意放的,当然,肯定还有不少不明显的bug ),有兴趣的朋友可以当做作业来检验一下自己的水平。

因为今天要掌握的内容比较多,所以,明天暂停一天,给大家一个消化的机会(我也顺便偷个懒)。。。如果有疑问,请用下面的“我要提问”连接提出。

现在轻松啦,可以上床虎虎了。。。

==============================================================

第四天:休息,休息一下

今天大家可要好好消化一下昨天的东西啦。。正好,我也歇歇。对了,有不明白的,点下面的“我要提问”连接提出,我会在论坛里解答。毕竟这里的“我要评论”显示效果差一些,也不能查询。谢谢大家合作 ^ ^

赞(0)
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com 特别注意:本站所有转载文章言论不代表本站观点! 本站所提供的图片等素材,版权归原作者所有,如需使用,请与原作者联系。未经允许不得转载:IDC资讯中心 » ASP无组件上传·从原理剖析到实践(上)-ASP教程,ASP应用
分享到: 更多 (0)