2012-03-19 12 views
11

Existen muchas soluciones de carga de archivos multipart/form-data, pero no he podido encontrar una de forma gratuita para Scala.Analizando datos de formularios HTTP de varias partes con contenido de carga de archivos usando Scala

Play2 tiene esta funcionalidad como parte del marco y Spray también admite datos de formularios de varias partes. Lamentablemente, ambos parecen estar bastante integrados en el resto de los conjuntos de herramientas (puedo estar equivocado aquí).

Mi servidor ha sido desarrollado utilizando Finagle (que actualmente no es compatible con datos de formularios de varias partes), y si es posible, me gustaría utilizar una solución libre o 'rodar por mi cuenta'.

Este es un mensaje típico 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-- 

En este ejemplo, *****org.apache.cordova.formBoundary es la forma límite, por lo que la carga de varias contiene 2 parámetros de texto y una imagen (I concatenan los datos de imagen para claridad).

Si alguien que conoce a Scala mejor que yo puede darme un poco de un resumen sobre cómo abordar el análisis de este contenido, le estaré muy agradecido.

Para empezar, pensé que iba a dividir rápidamente el contenido de cada tres haciendo:

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

Pero la ejecución es notablemente lenta (actualización - esto era debido al tiempo de calentamiento). ¿Hay una manera más eficiente de dividir las partes? Mi estrategia es dividir el contenido en partes y dividir las partes en subpartes. ¿Es este un enfoque horrible? ¿He visto problemas similares que se resuelven con las máquinas de estado? ¿Cuál es un buen enfoque funcional? Tenga en cuenta que estoy tratando de aprender un enfoque adecuado para Scala al tratar de resolver el problema.

Actualización:

Realmente pensó que una solución a este problema sería una línea o dos en Scala. Si alguien se tropieza con esta pregunta con una solución ingeniosa, tómese el tiempo para anotarla. Desde mi punto de vista, uno podría analizar este mensaje usando la coincidencia de patrones, el análisis de los combinadores, la extracción o simplemente dividiendo la cadena. Estoy tratando de encontrar la mejor manera de resolver este tipo de problema, ya que un proyecto en el que estoy trabajando implica una gran cantidad de análisis de lenguaje natural, y tengo que escribir mis propias herramientas de análisis personalizadas. Entiendo bien Scala, pero nada supera el consejo de un experto.

No se trata solo de resolver el problema, se trata de encontrar la mejor (y afortunada, la más simple) forma de resolver este tipo de problema.

+0

Puede encontrar el código Play aquí https://github.com/playframework/Play20/blob/master/framework/src/play/src/main/scala/play/api/ mvc/ContentTypes.scala Parece razonablemente comprensible –

+0

Gracias @Paul. Eché un vistazo al código de juego, gracias por el enlace. Entiendo la mayor parte, sin embargo, es un poco complicado para lo que estoy tratando de hacer. Solo busco la manera más simple de separar los tres paquetes de datos anteriores y acceder al contenido de cada paquete. ¿Algún tipo de división anidada basada en expresiones regulares puede hacer el truco? – Jack

+0

Sugeriría volver a plantear esta pregunta: lo he ignorado por un tiempo como específico para la carga de archivos y cosas, mientras que parece ser una pregunta general sobre el análisis sintáctico. He repetido para indicar esto, pero un título que deja en claro que se trata de escribir un analizador probablemente atraerá más respuestas. – Submonoid

Respuesta

0

Esta es probablemente la peor solución posible, y no escalable en cualquier forma, pero apenas para conseguir rápidamente la imagen los datos de la solicitud de varias partes hice lo siguiente (si alguien le da una mejor respuesta, voy a anular la marca de mi respuesta):

// 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) 

voy a tratar de mejorar en esto más adelante, pero tienen que seguir adelante por ahora. Otro día, otro proyecto: -o

1

Tengo curiosidad sobre lo lento que es su "notablemente lento" en realidad. Escribí el siguiente poco simple función para generar mensajes falsos:

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--" 
} 

siguiente que crea un mensaje razonablemente grande a utilizar para la prueba:

val data = generateFakeMessage(10000) 

termina contiene un poco más de medio millón de líneas. Luego probé su expresión regular:

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

Y vuelve más o menos instantáneamente.Es probable que pueda sintonizar un poco la expresión regular, y hay enfoques más limpios que podría utilizar si sus datos fueran un Iterable[String] sobre las líneas del mensaje, pero no creo que vaya a obtener un mejor rendimiento de un rollo manual máquina de estado para analizar un gran String.

+0

Gracias, Travis. Estaba siendo tonto y probé mi código como un script de Scala. No me di cuenta de que seguía incurriendo en una penalización inicial. En realidad, es muy impresionante ver qué tan rápido se ejecuta tu código. Mantengo la pregunta abierta con la esperanza de que alguien me guíe en mi problema de análisis sintáctico. Espero que esto esté bien. – Jack

0

Para una primera sugerencia, this question da dos sugerencias, una usando una máquina de estados, y la otra usando los combinadores de analizadores. Prestaré especial atención a la respuesta utilizando los combinadores de analizadores, ya que estos proporcionan una manera muy fácil de construir este tipo de analizador sintáctico. La sintaxis proporcionada en la respuesta de Daniel debe adaptarse muy fácilmente a su situación.

Además, puede proporcionar asignaciones más específicas en Scala para su gramática particular, si así lo requiere. Donde Daniel tiene:

campo def = (fieldName < ~ ":") ~ ~ fieldBody < CRLF ^^ {nombre del caso ~ cuerpo => Nombre -> cuerpo}

se puede sustituir este con un patrón de alternancia en múltiples campos (contentType|contentDisposition|....) y mapee cada uno de estos individualmente en sus objetos Scala.

Disculpa las molestias por no tener tiempo para escribir una solución más detallada aquí, ¡pero esto debería indicarte la dirección correcta!

+0

No he usado combinadores de analizadores, pero ahora es un momento tan bueno como cualquier otro, supongo ;-) Tropecé con esa solución después de hacer esta pregunta, pero la pregunta tenía la limitación de no querer cargar todo el archivo en la memoria. No tengo esta restricción, así que pensé que podría haber una mejor manera. Realmente estoy buscando una solución simple, porque sé que debe haber una. Mientras tanto, le daré otra mirada, gracias, y gracias por el consejo. Solo quiero asegurarme de haber explorado todas las vías, porque necesito analizar mucho en un próximo proyecto. – Jack

+0

Para una solución realmente simple, probablemente esté bien con su método de división. Donde los combinators son útiles es construir analizadores complejos desde simples. Mientras que los dados en la respuesta de Daniel pueden parecer complejos, cada parte individual es muy simple, por lo que puedes construir un analizador a partir de piezas en lugar de tratar de asimilar toda la gramática de una vez. – Submonoid

0

creo que su solución:

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

que es O (n) en complejidad, es la mejor y la más simple que puede obtener. Como dijo Travis anteriormente, esta manipulación no es lenta. Como siempre con un formulario HTTP de varias partes, tendrá que analizarlo de una forma u otra y hacer mejor a O (n) parece complicado.

Además, como split que proporciona una Iterable es realmente perfecto para cualquier juego, el tratamiento ...

Cuestiones relacionadas