<form id="hz9zz"></form>
  • <form id="hz9zz"></form>

      <nobr id="hz9zz"></nobr>

      <form id="hz9zz"></form>

    1. 明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

      ASP無組件上傳·從原理剖析到實戰(上)

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

      無組件上傳一直是困擾大家的一個問題。其實原理很簡單,核心就是分析字符串。但是,實際操作時,卻困難重重。其中的關鍵問題還是大家往往對原理的剖析不夠深入,或者是因為過程過于繁瑣,導致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來循環讀取,所以是沒問題的,不容易出錯。

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

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


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

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


      日韩精品一区二区三区高清