Разбор multipart данных формы HTTP с файлом загрузки содержимого с помощью Scala

Есть много multipart/form-data для загрузки файлов, но я не смог найти бесплатную версию для Scala.

Play2 имеет эту функциональность как часть фреймворка, а Spray также поддерживает данные многостраничной формы. К сожалению, обе эти функции, по-видимому, довольно интегрированы в остальные инструменты (я могу ошибаться здесь).

Мой сервер был разработан с использованием Finagle (который в настоящее время не поддерживает данные мультиплатформенной формы), и, если возможно, я хотел бы использовать бесплатное решение lib или "roll my own".

Это типичное сообщение multipart/form-data:

--*****org.apache.cordova.formBoundary
Content-Disposition: form-data; name="value1"

First parameter content
--*****org.apache.cordova.formBoundary
Content-Disposition: form-data; name="value2"

Second parameter content
--*****org.apache.cordova.formBoundary
Content-Disposition: form-data; name="file"; filename="image.jpg"
Content-Type: image/jpeg

$%^&#$%^%#$
--*****org.apache.cordova.formBoundary--

В этом примере *****org.apache.cordova.formBoundary является границей формы, поэтому многостраничная загрузка содержит 2 текстовых параметра и одно изображение (я конкатенировал данные изображения для ясности).

Если кто-то, кто знает Scala лучше меня, может немного рассказать о том, как подойти к разбору этого контента, я буду очень благодарен.

Для начала я подумал, что быстро разделил контент на три:

data.split("\\Q--*****org.apache.cordova.formBoundary\\E") foreach println

Но выполнение заметно медленное (обновление - это было вызвано разминкой). Есть ли более эффективный способ разделить детали? Моя стратегия состоит в том, чтобы разделить содержимое на части и разделить части на части. Это дрянной подход? Я видел, как подобные проблемы решаются с помощью государственных машин? Что такое хороший функциональный подход. Имейте в виду, я пытаюсь изучить правильный подход к Scala, пытаясь решить проблему.

Update:

Я действительно думал, что решение этой проблемы будет состоять из строки или двух в Scala. Если кто-то споткнется об этом вопросе с помощью гладкого решения, пожалуйста, найдите время, чтобы записать его. Из моего понимания можно было разобрать это сообщение, используя сопоставление шаблонов, разбор комбинаторов, извлечение или просто разделение строки. Я пытаюсь найти лучший способ решить эту проблему, так как проект, который я работаю, включает в себя много естественного разбора языка, и мне нужно написать собственные инструменты для анализа. Я хорошо понимаю Scala, но ничто не сравнится с советом эксперта.

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

Ответ 1

Это, пожалуй, самое худшее возможное решение, а не масштабируемое каким-либо образом, но чтобы быстро получить данные изображения из многостраничного запроса, я сделал следующее (если кто-то даст лучший ответ, я отменим мой ответ):

// Take the request and split it into parts
var requestParts = request.content.toString(UTF_8).split("\\Q--*****org.apache.cordova.formBoundary\\E")
// Split the third part at the blank line
val imageParts = requestParts(3).split("\\n\\s*\\n")
// The part above the blank line is the header text
val imageHeader = imageParts(0)
// The part below the blank line is the image body
val imageBodyString = imageParts(1)

Я попытаюсь улучшить это позже, но сейчас нужно продвигаться вперед. Еще один день, другой проект: -o

Ответ 2

Мне интересно, насколько медленным является ваш "заметно медленный". Я написал следующую небольшую функцию для генерации фальшивых сообщений:

def generateFakeMessage(n: Int) = {
  val rand = new scala.util.Random(1L)
  val maxLines = 100
  val maxLength = 100

  (1 to n).map(i =>
    "--*****org.apache.cordova.formBoundary\n" +
    "Content-Disposition: form-data; name=\"value%d\"\n\n".format(i) +
    (0 to rand.nextInt(maxLines)).map(_ =>
      (0 to rand.nextInt(maxLength)).map(_ => rand.nextPrintableChar).mkString
    ).mkString("\n")
  ).mkString("\n") + "\n--*****org.apache.cordova.formBoundary--"
}

Затем я создал достаточно большое сообщение для тестирования:

val data = generateFakeMessage(10000)

В итоге он содержит чуть более полумиллиона строк. Затем я попробовал ваше регулярное выражение:

data.split("\\Q--*****org.apache.cordova.formBoundary\\E").size

И он возвращается более или менее мгновенно. Возможно, вы можете немного настроить регулярное выражение, и есть более чистые подходы, которые вы могли бы использовать, если ваши данные были Iterable[String] по строкам сообщения, но я не думаю, что вы получите лучшую производительность от руки -rolled state machine для разбора одного большого String.

Ответ 3

Для первого предложения этот вопрос дает два предложения: один использует конечный автомат, а другой - с помощью комбинаторов-парсеров. Я бы обратил особое внимание на ответ с помощью комбинаторов парсеров, поскольку они обеспечивают очень простой способ создания такого анализатора. Синтаксис, приведенный в ответе Даниэля, должен очень легко адаптироваться к вашей ситуации.

Кроме того, вы можете предоставить более конкретные сопоставления в Scala для вашей конкретной грамматики, если потребуется. Где Даниэль:

def field = (fieldName < ~ ":" ) ~ fieldBody < ~ CRLF ^^ {имя случая ~ body = > имя → тело}

вы можете заменить это на шаблон чередования по нескольким полям (contentType|contentDisposition|....) и сопоставить каждый из них по отдельности в своих объектах Scala.

Извиняется за то, что у нас нет времени для написания более детального решения, но это, надеюсь, укажет вам в правильном направлении!

Ответ 4

Я думаю, что ваше решение:

data.split("\\Q--*****org.apache.cordova.formBoundary\\E") foreach println

который является O (n) по сложности, является лучшим и самым простым, что вы можете получить. Как ранее сказал Тревис, эта манипуляция не медленная. Как всегда с многостраничной формой HTTP, вам придется анализировать ее так или иначе, и лучше делать O (n) представляется сложным.

Кроме того, поскольку split предоставляет вам Iterable, он действительно идеален для любого соответствия, лечения...