Разбор 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
, он действительно идеален для любого соответствия, лечения...