明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

ASP無組件上傳·從原理剖析到實(shí)戰(zhàn)(上)

[摘要]無組件上傳一直是困擾大家的一個(gè)問題。其實(shí)原理很簡單,核心就是分析字符串。但是,實(shí)際操作時(shí),卻困難重重。其中的關(guān)鍵問題還是大家往往對原理的剖析不夠深入,或者是因?yàn)檫^程過于繁瑣,導(dǎo)致bug不斷。一直以來,都想做一個(gè)完善的例子,只不過想想就頭痛,加上沒時(shí)間(借口,呵呵 ),所以沒有付諸行動(dòng)。 今天就咬咬...

無組件上傳一直是困擾大家的一個(gè)問題。其實(shí)原理很簡單,核心就是分析字符串。但是,實(shí)際操作時(shí),卻困難重重。其中的關(guān)鍵問題還是大家往往對原理的剖析不夠深入,或者是因?yàn)檫^程過于繁瑣,導(dǎo)致bug不斷。一直以來,都想做一個(gè)完善的例子,只不過想想就頭痛,加上沒時(shí)間(借口,呵呵 ),所以沒有付諸行動(dòng)。

今天就咬咬牙,給大家提供一個(gè)完整的無組件上傳的例子。因?yàn)楸救四托圆缓,所以咱們一點(diǎn)一點(diǎn)來,分幾天完成。未來的幾天,我會(huì)天天更新這個(gè)文檔,這個(gè)過程也是大家學(xué)習(xí)和提高的過程。

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

==============================================================
第一天:認(rèn)識我們的解剖對象——數(shù)據(jù)

上傳文件時(shí),首先要知道我們得到的是什么。下面是一個(gè)上傳文件的表單,我們就從他開始。
<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"的意思,是設(shè)置表單的MIME編碼。默認(rèn)情況,這個(gè)編碼格式是application/x-www-form-urlencoded,不能用于文件上傳;只有使用了multipart/form-data,才能完整的傳遞文件數(shù)據(jù),進(jìn)行下面的操作(有興趣的朋友,可以自己試試看兩者的異同。方法很簡單,就是把這一句去掉),F(xiàn)在,我們在表單中分別填入數(shù)據(jù):
file1的說明 D:\我的 圖片\BACK046.GIF
file2的說明 D:\我的 圖片\BACK293.GIF

這里用了中英文、空格混排。目的是讓例子更有一般性。我選的這兩個(gè)圖片分別是54和62字節(jié)。大圖片的原理完全一樣,不過小圖片做例子更合適些,原理容易展現(xiàn)。
為了看到我們得到的數(shù)據(jù),在doupload.asp里,有這幾行代碼:
<%
formsize=request.totalbytes
formdata=request.binaryread(formsize)
response.BinaryWrite(formdata)
%>

很簡單,作用就是打出來傳過來的所有數(shù)據(jù)。如果不熟悉,你可以先研究一下request和response對象的這兩個(gè)方法。

提交表單,我們在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--

不用懷疑,這就是你從上一個(gè)“簡單”表單傳過來的東西。現(xiàn)在想想看,怎么對付這一堆東西?是不是看上去有規(guī)律,又不知道從何下手?明天,咱們就分析一下這堆“圖片”,看看怎么分離出我們要的內(nèi)容。


==============================================================
第二天:分拆初步

睡了個(gè)好覺,大家腦子清醒多了吧?今天中午吃的火鍋,阿森納vs.鐵哥也沒看完,現(xiàn)在一腦子大油。。。
OK,咱們繼續(xù)研究這個(gè)枯燥的問題。首先,要找出規(guī)律。看上去似乎很簡單,就是用
-----------------------------7d22131090458
做分隔,這樣,每一個(gè)文本單元里,都是
Content-Disposition: form-data; name="表單域的名字";

表單域的內(nèi)容

而每一個(gè)文件單元里,都是
Content-Disposition: form-data; name="表單域的名字"; filename="文件全路徑"
Content-Type: 文件類型

文件的二進(jìn)制內(nèi)容

那么,是不是直接用
split(formdata,"-----------------------------7d22131090458")
就可以得到各個(gè)單元了呢?答案是否定的。首先,formdata不是字符串而是二進(jìn)制串,不能用split的方法;其次,這里的7d22131090458并不固定,每次都會(huì)有變化,并不適合做分隔符。所以,應(yīng)該用一個(gè)更保險(xiǎn)的辦法。想到?jīng)]?很簡單——就用formdata的第一行做分隔符。只要用instrb函數(shù)得到換行符的位置,然后用leftb或midb函數(shù)截取數(shù)據(jù)就行了。我們動(dòng)手試試:
<%
' 二進(jìn)制的回車<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)
%>

運(yùn)行。。。成功了!得到了需要的divider。注意,這里的字符串函數(shù)都是針對二進(jìn)制數(shù)據(jù)操作的,所以,用的是他們的二進(jìn)制版,加了“b”(binary的首字母)——instrb,leftb(以后可能還出現(xiàn)rightb,midb,lenb。。等等)。畢竟,formdata是用“binaryread()”得到的嘛。好了,有的分隔符,就可以得到數(shù)據(jù)了。我們從簡單的開始,先拿第一個(gè)單元出來看看,目標(biāo)是得到表單域名稱和數(shù)據(jù)。
<%
' 這是回車<return>
bncrlf=chrB(13) & chrB(10)

' 得到數(shù)據(jù)
formsize=request.totalbytes
formdata=request.binaryread(formsize)

' 得到divider,分隔符
divider=leftB(formdata,clng(instrb(formdata,bncrlf))-1)

' 起始位置
startpos = instrb(formdata,divider)+lenb(divider)+lenb(bncrlf)
' 終止位置,從起始位置開始到下一個(gè)divider
endpos = instrb(startpos, formdata, divider)-lenb(bncrlf)
part1 = midb(formdata, startpos, endpos-startpos)
response.BinaryWrite(part1)
%>

這一段有注釋,相信大家沒問題。如果對這些函數(shù)不了解,可以到http://www.2yup.com/asp/referrence/index.asp下載msdn參考看看vbscript的函數(shù)用法,對提高水平有很大幫助。
這時(shí)候得到的結(jié)果可以通過查看生成的html源的方式看到:
Content-Disposition: form-data; name="file1_desc"

file1的說明

好了,離成功又進(jìn)一步!
下來只要分別讀取part1里name="和第一個(gè)“雙引號+回車”之間的內(nèi)容就可以得到表單域的名稱;讀取連續(xù)兩個(gè)回車之后的內(nèi)容就可以得到表單域的值了。下面一段順理成章:
<%
' 這就是name="
const_nameis=chrb(110)&chrb(97)&chrb(109)&chrb(101)&chrb(61)&chrb(34)
' 這是回車<return>
bncrlf=chrB(13) & chrB(10)

' 得到數(shù)據(jù)
formsize=request.totalbytes
formdata=request.binaryread(formsize)

' 得到divider,分隔符
divider=leftB(formdata,clng(instrb(formdata,bncrlf))-1)

' 起始位置
startpos = instrb(formdata,divider)+lenb(divider)+lenb(bncrlf)
' 終止位置,從起始位置開始到下一個(gè)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))

' 檢查一下?可以每次打開一個(gè)注釋,分別檢查。
'response.binarywrite(fldname)
'response.binarywrite(fldvalue)
%>

執(zhí)行一下?呵呵,沒問題啦,分別打開注釋,會(huì)在IE里看到“file1_desc”和“file1的說明”。
當(dāng)然,這是得到文本單元的方法,不過看看上邊的原始數(shù)據(jù)就知道,得到文件單元方法可以說是基本相同,只不過:
1。需要額外得到filename=""里的值,也就是文件全路徑;
2。需要額外得到Content-Type: 后邊的值,也就是文件的類型。
這個(gè)工作就是體力勞動(dòng)了,相信大家沒問題,F(xiàn)在更大的精力應(yīng)該放在:怎么得到所有的段落內(nèi)容?想來應(yīng)該是某種形式的循環(huán),但是,具體怎么做?還有,怎么樣組織得到的東西,才不顯得凌亂?

呵呵,不早了,這個(gè)就是咱今天晚上要做的夢了。明天來,咱就一起解決這個(gè)問題。。。。


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

wake up!繼續(xù)啦~~~~~
昨天,我們已經(jīng)找到了得到一個(gè)單元的信息的辦法,不過,還沒有到實(shí)用階段。畢竟,想實(shí)用,還至少要:
對于文本單元,能按名稱檢索的內(nèi)容;
對于文件單元,能按名稱得到文件的具體內(nèi)容、類型、全路徑、以及大小等信息。
今天,我們就首先著手解決文本單元的問題。

得到內(nèi)容也許不難,可是,怎么組織才能使這個(gè)過程井井有條,才能符合我們的一般習(xí)慣?我們可以從現(xiàn)有的知識里找答案。
大家都知道,asp有一個(gè)內(nèi)置對象request,他的功能是得到用戶請求的相關(guān)信息,包括表單域的值。粗看上去,他的form集合的用法和我們要實(shí)現(xiàn)的得到文本單元內(nèi)容的功能是很近似的,我們來看看request.form的幾個(gè)應(yīng)用的例子:

得到表單域的值 -
request.form("表單域名稱")或request.form("表單域在<form></form>里的序號")
得到同名表單域的各個(gè)元素 -
request.form("表單域名稱")(i)或request.form("表單域在<form></form>里的序號")(i)
得到同名表單域的個(gè)數(shù) -
request.form("表單域名稱").Count或request.form("表單域在<form></form>里的序號").Count

如果我們能夠用ourRequest.form("name"),ourRequest.form(index),ourRequest.form("name").count,ourRequest.form(index).count這樣的方式,或是與之相近的方式,不就可以很好的和request對象對應(yīng)起來么?而且,因?yàn)閷equest對象本身的熟悉,也會(huì)降低使用我們自己方法的時(shí)候的門檻,相對于寫一堆getValue(name)函數(shù)這樣的方法,更不容易出錯(cuò),擴(kuò)展性更好更靈活,可讀性也好得多。那么,我們就看看如果要實(shí)現(xiàn)自己的request對象,都有哪些工作要做。

首先,ourRequest應(yīng)該是一個(gè)對象,有自己的屬性和方法。只有這樣,才可能和現(xiàn)有的request對象做呼應(yīng)。在vbs5里面,已經(jīng)可以通過Class關(guān)鍵字,來實(shí)現(xiàn)自己的類了,所以,可行性上是沒有問題的,只要我們自己定義一個(gè)類,然后實(shí)例化他,就可以得到我們所需的對象;
其次,因?yàn)閛urRequest.form可以用名稱和序號檢索,所以,應(yīng)該提供比較豐富的訪問方式;
第三,在表單里有多個(gè)域名稱相同的時(shí)候(比如多個(gè)checkbox),應(yīng)該能夠得到其中的各個(gè)元素,并且可以得到總個(gè)數(shù)。所以,ourRequest.form()得到的,應(yīng)該也是一個(gè)可以檢索的對象,而且有Count屬性。

最終,結(jié)合vbscript的語言特點(diǎn),兼顧開發(fā)效率,我們決定實(shí)現(xiàn)這樣的幾個(gè)類:
A。UploadRequest
這個(gè)類和request對象是對應(yīng)的
屬性:
RawData 得到原始數(shù)據(jù),方便檢查[只讀]
Forms 得到一個(gè)有count屬性的計(jì)數(shù)器,
可以用outRequest.Forms.Count的方式,得到文本表單域的的個(gè)數(shù)[只讀]
Form(index) 可以用數(shù)字或文本檢索文本表單域,做用類似request.form。
他返回一個(gè)FormElement型的對象
B。FormElement
可以把它看成單個(gè)表單域的化身。通過這個(gè)類,可以得到詳細(xì)的表單域信息,比如name,value等等。如果有多個(gè)value(比如checkbox的情況),還可以選擇用序號索引
屬性:
Value 得到表單域的值。如果有多個(gè)(比如checkbox),
返回所有的,用逗號分隔[默認(rèn)]
Name 得到表單域的名稱
Item(index) 用數(shù)字索引多個(gè)值中的某一個(gè)
Count 得到對應(yīng)一個(gè)name,所擁有的value的個(gè)數(shù)。主要用于checkbox[只讀]
C。Counter
一個(gè)輔助類,就是為了實(shí)現(xiàn)outRequest.Forms.Count功能。這里寫的并不好,不過考慮大家的理解方便,先暫時(shí)這樣。
屬性:
Count 得到Count
方法:
setCount 設(shè)置Count


下面,我們就來看看這幾個(gè)類的實(shí)現(xiàn):
<%
Class FormElement

' m_開頭,表示類成員變量。
Private m_dicItems

Private Sub Class_Initialize()
Set m_dicItems = Server.CreateObject("Scripting.Dictionary")
End Sub

' count是咱們這個(gè)類的一個(gè)只讀屬性
Public Property Get Count()
Count = m_dicItems.Count
End Property

' Value是一個(gè)默認(rèn)屬性。目的是得到值
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)的某一個(gè)值
Public Property Get Item(index)
If isNumeric(index) Then '是數(shù)字,合法!
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 '給個(gè)一個(gè)不是數(shù)字的東東?出錯(cuò)!
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

' 有了這個(gè),就可以檢查原始數(shù)據(jù)了
Public Property Get RawData()
RawData = m_bFormdata
End Property

' 這一段丑陋的代碼是為了實(shí)現(xiàn)outRequest.Forms.Count這個(gè)功能。
Public Property Get Forms()
Set Forms = New Counter
Forms.setCount(m_dicForms.Count)
End Property

Public Property Get Form(index)
If isNumeric(index) Then '是數(shù)字?用數(shù)字來檢索
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 '不存在,就給個(gè)空值——request對象就是這么做的。
Exit Property
End If
Else '給了一個(gè)不是數(shù)字也不是字符串的東東?出錯(cuò)!
err.raise 2,"IllegalArgument", "非法的表單元素索引"
End If
End Property

Private Sub fill
' 得到數(shù)據(jù)
m_bFormdata=request.binaryread(request.totalbytes)
' 調(diào)用這個(gè)函數(shù)實(shí)現(xiàn)遞歸循環(huán),讀取文本單元
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)
' 終止位置,從起始位置開始到下一個(gè)divider
endpos = instrb(startpos, data, divider)-lenb(bncrlf)
If endpos < 1 Then '沒有下一個(gè)了!結(jié)束!
Exit Sub
End If
part1 = midb(data, startpos, endpos-startpos)
' 得到part1的第一行
firstline = midb(part1, 1, instrb(part1, bncrlf)-1)

'沒有filename=",有name=",說明是一個(gè)文本單元(這里有一個(gè)BUG,自己研究一下?當(dāng)作業(yè)吧)
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

' 截取剩下的部分,遞歸調(diào)用這個(gè)函數(shù),來得到下一個(gè)part1。
Call fillEveryFirstPart(rightb(data, lenb(data)-endpos-1))
End Sub

' 這是一個(gè)公用函數(shù),作用是二進(jìn)制和字符串的轉(zhuǎn)換
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 '遇到了雙字節(jié),就得兩個(gè)字符一起處理
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

' 這是一個(gè)輔助類,為了實(shí)現(xiàn)outRequest.Forms.Count功能。
Class Counter
Private m_icnt

' count是咱們這個(gè)類的一個(gè)只讀屬性
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%>個(gè)文本單元

這里的注釋很詳細(xì),而且,每一個(gè)類的屬性和方法都很少,所以相信基礎(chǔ)好的朋友讀懂是沒有問題的。對應(yīng)的,我們的測試表單也改成了:
<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>

注意,這里的每一個(gè)文本表單域都要填上,因?yàn)闇y試碼給得很特殊,讀了0,1,2,3各個(gè)項(xiàng)目的值,測試了各個(gè)屬性。不過,現(xiàn)實(shí)情況下,因?yàn)槭孪戎辣韱斡虻拿Q;即使不知道,也可以用outRequest.Forms.Count來循環(huán)讀取,所以是沒問題的,不容易出錯(cuò)。

現(xiàn)在,試試看!怎么樣?成功了吧 呵呵,中英文都沒有問題,用法也很簡單,很清晰,F(xiàn)在,我們就可以說基本上解決了文本域的讀取問題。

--------------------------------------------------------
今天這一段是很有挑戰(zhàn)性的。我寫了兩個(gè)多小時(shí)。對于尚處于初級的朋友,可能會(huì)覺得有些吃力。其實(shí),關(guān)鍵在于深刻的理解類的概念,如果這一點(diǎn)沒有問題,那么,理解這些代碼就不在話下了。對了,今天的代碼里有一個(gè)比較明顯的BUG(我故意放的,當(dāng)然,肯定還有不少不明顯的BUG ),有興趣的朋友可以當(dāng)做作業(yè)來檢驗(yàn)一下自己的水平。
因?yàn)榻裉煲莆盏膬?nèi)容比較多,所以,明天暫停一天,給大家一個(gè)消化的機(jī)會(huì)(我也順便偷個(gè)懶)。。。如果有疑問,請用下面的“我要提問”連接提出。
現(xiàn)在輕松啦,可以上床虎虎了。。。


==============================================================
第四天:休息,休息一下

今天大家可要好好消化一下昨天的東西啦。。正好,我也歇歇。對了,有不明白的,點(diǎn)下面的“我要提問”連接提出,我會(huì)在論壇里解答。畢竟這里的“我要評論”顯示效果差一些,也不能查詢。謝謝大家合作 ^ ^