Пакетный загрузчик файлов на сайт.
To view this page ensure that Adobe Flash Player version 11.1.0 or greater is installed.
На этой страничке я расскажу о небольшом, но важном компоненте любого сайта - пакетном загрузчике. Дело в том, что стандартный элемент HTML (<input type="file") умеет загружать лишь один файлик, к тому же он кривой на всю голову (его даже стилями нельзя кастомизировать). Поэтому в инете расплодились всякие проекты замены стандартного HTML аплоадера на более удобные. Ну мне как программисту неинтересно убивать свое время на настройку и изучение параметров чужих аплоадеров, тем более я сам их пишу в один чих. Тут я опишу один из них.
Прежде всего я бы хотел заметить что FolderBrowse (то есть полная загрузка всей папки) существует только в AIR, а во флексе есть групповая загрузка произвольного числа файлов. И небольшое второе замечание в том, что этот же класс выполняет и обратную задачку - групповой Download большого количества файлов с сайта на кампутер к юзверю.
Теперь я покажу свой самый простой аплоадер. Он имеет один ключик (которым идентифицируются загружаемые рисунки) и позволяет набрать любое количество рисунков и загрузить их на сайт в один клик мышкой.
Как всегда это самый простой вариант - который легко гнется в любую сторону.
1: <?xml version="1.0" encoding="utf-8"?>
2: <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
3: xmlns:s="library://ns.adobe.com/flex/spark"
4: xmlns:mx="library://ns.adobe.com/flex/mx"
5: minWidth="500" minHeight="80" height="80" width="500"
6: applicationComplete="application1_applicationCompleteHandler(event)" >
7:
8: <fx:Script>
9: <![CDATA[
10:
11: [Embed(source="Button1.gif")]
12: [Bindable]
13: public var Button1:Class;
14:
15: import mx.events.FlexEvent;
16:
17: private var FileList:FileReferenceList;
18: private var OneFile:FileReference;
19: private var POSTRequest:URLRequest
20: private var MyPage:String ;
21: private var MyCookie:String
22: //private const FILE_UPLOAD_URL:String = "http://localhost:49524/FileStream2/LoadImage.ashx";
23: private const FILE_UPLOAD_URL:String = "http://XXXXXXXX.ru/LoadImage.ashx";
24:
25:
26: protected function application1_applicationCompleteHandler(event:FlexEvent):void
27: {
28: //этот компонент можно разместить только в определенном домене
29: MyPage= ExternalInterface.call("window.location.href.toString") ;
30: MyCookie = ExternalInterface.call("window.document.cookie.toString");
31: }
32:
33:
34: protected function image1_clickHandler(event:MouseEvent):void
35: {
36: var FILE_UPLOAD:String = FILE_UPLOAD_URL + "?Hotel=" + Txt1.text + "&Page=" + encodeURIComponent(MyPage) + "&Cookie=" + encodeURIComponent(MyCookie)
37: POSTRequest = new URLRequest(FILE_UPLOAD)
38: FileList = new FileReferenceList();
39: FileList.addEventListener(Event.SELECT, fileRef_select);
40: FileList.browse();
41: }
42:
43: private function fileRef_select(evt:Event):void {
44: try {
45: Progress1.visible = true;
46: Progress1.maximum = FileList.fileList.length ;
47: var j:int;
48: for each (OneFile in FileList.fileList){
49: //подготовились - вывели статистику
50: Msg1.text = numberFormatter.format(OneFile.size) + " bytes";
51: j++;
52: Progress1.setProgress(j,FileList.fileList.length);
53: //пошла загрузка
54: OneFile.upload(POSTRequest);
55: }
56: Progress1.visible = false;
57: Msg1.text = FileList.fileList.length + " files uploaded";
58:
59: } catch (err:Error) {
60: Progress1.visible = false;
61: Msg1.text = err.message;
62: }
63: }
64:
65:
66: ]]>
67: </fx:Script>
68:
69: <fx:Declarations>
70: <mx:NumberFormatter id="numberFormatter" />
71: </fx:Declarations>
72: <mx:Image x="419" y="0" width="80" height="80" source="{Button1}" buttonMode="true" click="image1_clickHandler(event)" toolTip="Выберите папку с рисунками"/>
73: <s:TextInput x="100" y="12" width="280" id="Txt1"/>
74: <mx:ProgressBar x="100" y="44" width="279" minimum="0" mode="manual" id="Progress1" labelPlacement="center" label=" " visible="false"/>
75: <s:Label x="13" y="19" text="Отель:"/>
76: <s:Label x="15" y="45" id="Msg1"/>
77: </s:Application>
Как видите, флексовый код простой предельно. Но кто примет это PostBack?
Чтобы правильно обработать этот Посбек, надо понять что он из себя представляет. Если прочитать постбек напрямую вот таким кодом:
1: Dim Byte1 As Byte() = context.Request.BinaryRead(context.Request.InputStream.Length)
2: My.Computer.FileSystem.WriteAllBytes(IO.Path.Combine(Dir, PostMultipartParser.Filename), PostMultipartParser.FileContents, False)
то можно увидеть его структуру:
После взгляда на эти рисунки становится ясно что флексовый компонент формирует самый что ни на есть обычный ContentType: multipart/form-data; boundary=--xxxx. Обработчика такого в сыром виде нет (точнее он куда-то глубоко запрятан и в ASP.NET и в ASP.NET MVC). Парсить самому этот пакет на фрагменты мне лень и я тыкнул в инете и взял готовый отсюда. Меня немного раздражает ублюдочный новомодный Шарп и я сконвертировал этот код для удобства в более приятный язык - в Бейсик.
Получилось вот что:
1: Imports Microsoft.VisualBasic
2: Imports System.IO
3: Imports System.Text
4: Imports System.Text.RegularExpressions
5:
6: ''' <summary>
7: ''' MultipartParser http://multipartparser.codeplex.com
8: ''' Reads a multipart form data stream and returns the filename, content type and contents as a stream.
9: ''' 2009 Anthony Super http://antscode.blogspot.com
10: ''' </summary>
11:
12: Public Class MultipartParser
13: Public Sub New(stream As Stream)
14: Me.Parse(stream, Encoding.UTF8)
15: End Sub
16:
17: Public Sub New(stream As Stream, encoding As Encoding)
18: Me.Parse(stream, encoding)
19: End Sub
20:
21: Private Sub Parse(stream As Stream, encoding As Encoding)
22: Me.Success = False
23:
24: ' Read the stream into a byte array
25: Dim data As Byte() = ToByteArray(stream)
26:
27: ' Copy to a string for header parsing
28: Dim content As String = encoding.GetString(data)
29:
30: ' The first line should contain the delimiter
31: Dim delimiterEndIndex As Integer = content.IndexOf(vbCr & vbLf)
32:
33: If delimiterEndIndex > -1 Then
34: Dim delimiter As String = content.Substring(0, content.IndexOf(vbCr & vbLf))
35:
36: ' Look for Content-Type
37: Dim re As New Regex("(?<=Content\-Type:)(.*?)(?=\r\n\r\n)")
38: Dim contentTypeMatch As Match = re.Match(content)
39:
40: ' Look for filename
41: re = New Regex("(?<=filename\=\"")(.*?)(?=\"")")
42: Dim filenameMatch As Match = re.Match(content)
43:
44: ' Did we find the required values?
45: If contentTypeMatch.Success AndAlso filenameMatch.Success Then
46: ' Set properties
47: Me.ContentType = contentTypeMatch.Value.Trim()
48: Me.Filename = filenameMatch.Value.Trim()
49:
50: ' Get the start & end indexes of the file contents
51: Dim startIndex As Integer = contentTypeMatch.Index + contentTypeMatch.Length + (vbCr & vbLf & vbCr & vbLf).Length
52:
53: Dim delimiterBytes As Byte() = encoding.GetBytes(vbCr & vbLf & delimiter)
54: Dim endIndex As Integer = IndexOf(data, delimiterBytes, startIndex)
55:
56: Dim contentLength As Integer = endIndex - startIndex
57:
58: ' Extract the file contents from the byte array
59: Dim fileData As Byte() = New Byte(contentLength - 1) {}
60:
61: Buffer.BlockCopy(data, startIndex, fileData, 0, contentLength)
62:
63: Me.FileContents = fileData
64: Me.Success = True
65: End If
66: End If
67: End Sub
68:
69: Private Function IndexOf(searchWithin As Byte(), serachFor As Byte(), startIndex As Integer) As Integer
70: Dim index As Integer = 0
71: Dim startPos As Integer = Array.IndexOf(searchWithin, serachFor(0), startIndex)
72:
73: If startPos <> -1 Then
74: While (startPos + index) < searchWithin.Length
75: If searchWithin(startPos + index) = serachFor(index) Then
76: index += 1
77: If index = serachFor.Length Then
78: Return startPos
79: End If
80: Else
81: startPos = Array.IndexOf(Of Byte)(searchWithin, serachFor(0), startPos + index)
82: If startPos = -1 Then
83: Return -1
84: End If
85: index = 0
86: End If
87: End While
88: End If
89:
90: Return -1
91: End Function
92:
93: Private Function ToByteArray(stream As Stream) As Byte()
94: Dim buffer As Byte() = New Byte(32767) {}
95: Using ms As New MemoryStream()
96: While True
97: Dim read As Integer = stream.Read(buffer, 0, buffer.Length)
98: If read <= 0 Then
99: Return ms.ToArray()
100: End If
101: ms.Write(buffer, 0, read)
102: End While
103: End Using
104: End Function
105:
106: Public Property Success() As Boolean
107: Get
108: Return m_Success
109: End Get
110: Private Set(value As Boolean)
111: m_Success = value
112: End Set
113: End Property
114: Private m_Success As Boolean
115:
116: Public Property ContentType() As String
117: Get
118: Return m_ContentType
119: End Get
120: Private Set(value As String)
121: m_ContentType = value
122: End Set
123: End Property
124: Private m_ContentType As String
125:
126: Public Property Filename() As String
127: Get
128: Return m_Filename
129: End Get
130: Private Set(value As String)
131: m_Filename = value
132: End Set
133: End Property
134: Private m_Filename As String
135:
136: Public Property FileContents() As Byte()
137: Get
138: Return m_FileContents
139: End Get
140: Private Set(value As Byte())
141: m_FileContents = value
142: End Set
143: End Property
144: Private m_FileContents As Byte()
145: End Class
Теперь, когда постбек можно поделить данным классом на отдельные постбечные переменные - дело остается за малым - собственно хандлером.
В простейшем виде он может выглядеть так:
1: <%@ WebHandler Language="VB" Class="LoadImage" %>
2:
3: Imports System
4: Imports System.Web
5: Imports System.Data
6:
7: Public Class LoadImage : Implements IHttpHandler
8:
9: Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
10: Try
11: If context.Request.RequestType = "POST" Then
12: Dim Hotel As String = context.Request.QueryString("Hotel")
13: 'Dim Byte1 As Byte() = context.Request.BinaryRead(context.Request.InputStream.Length)
14: Dim Dir As String = HttpContext.Current.Server.MapPath("~")
15: Dim PostMultipartParser As MultipartParser = New MultipartParser(context.Request.InputStream)
16: If PostMultipartParser.Success Then
17: My.Computer.FileSystem.WriteAllBytes(IO.Path.Combine(Dir, PostMultipartParser.Filename), PostMultipartParser.FileContents, False)
18: End If
19: context.Response.ContentType = "text/plain"
20: context.Response.Write("OK")
21: Else
22: context.Response.ContentType = "text/plain"
23: context.Response.Write("Ready")
24: End If
25:
26: Catch ex As Exception
27: SaveErrLog(ex.Message)
28: context.Response.ContentType = "text/plain"
29: context.Response.Write(ex.Message)
30: End Try
31: End Sub
32:
33:
34: Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
35: Get
36: Return False
37: End Get
38: End Property
39:
40: End Class
Разумеется, это лишь предельный (самый простой) вариант загрузчика. В реальном загрузчике вам как минимум придется проверить в хандлере на какой страничке лежал этот загрузчик, какие там куки (кто залогинился)и так далее. Но это будет уже не простой учебный загрузчик (который я описываю здесь для начинающих), а вполне себе коммерческая разработка.
|