(Flex) Flex (2012 год)

Пакетный загрузчик файлов на сайт.

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

Разумеется, это лишь предельный (самый простой) вариант загрузчика. В реальном загрузчике вам как минимум придется проверить в хандлере на какой страничке лежал этот загрузчик, какие там куки (кто залогинился)и так далее. Но это будет уже не простой учебный загрузчик (который я описываю здесь для начинающих), а вполне себе коммерческая разработка.



Comments ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17>  <18>  <19>  <20>  <21>  <22>  <23
Link to this page: //www.vb-net.com/FolderUploader/index.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <CHAT ME>  <ABOUT ME>  < THANKS ME>