Parameters in the POST request body are skipped. Only query parameters are accepted.

1,896 views
Skip to first unread message

Y Kamesh Rao

unread,
Jul 27, 2011, 6:18:29 AM7/27/11
to spray-user
I was sending request parameters in the POST request body and Spray
was shouting about the parameters being missing. It was only accepting
parameters only when I was sending them in query. Any way around to
how to fix this or is this the expected behaviour?

Mathias

unread,
Jul 27, 2011, 11:57:44 AM7/27/11
to spray...@googlegroups.com
The "parameters" directive only works on query parameters.
If you'd like to unmarshall the request entity body you'll have to use the "content" directive.

Btw. we will release spray 0.7.0 incl. a vastly updated documentation today.
This should hopefully clear up things even more...

Cheers,
Mathias

---
mat...@spray.cc
http://www.spray.cc

Dirk Louwers

unread,
Jul 28, 2011, 9:40:11 AM7/28/11
to spray-user
I have created a fairly generic Unmarshaller for application/x-www-
form-urlencoded data, will post it here in the hopes it will benefit
someone or even make it into the next release:

import cc.spray.marshalling.UnmarshallerBase
import cc.spray.http._
import MediaTypes._
import MediaRanges._
import HttpCharsets._
import java.net.URLDecoder
/**
* User: dirk
* Date: 28-07-11
* Time: 14:19
*/

trait PostUnmarshaller {

implicit object StringMapUnmarshaller extends
UnmarshallerBase[Map[String, Option[String]]] {
val canUnmarshalFrom = ContentTypeRange(`application/x-www-form-
urlencoded`) :: Nil

def unmarshal(content: HttpContent) = {
val charset = content.contentType.charset.getOrElse(`UTF-8`)
var buffer: Array[Byte] = new Array[Byte](content.length)
content.inputStream.read(buffer)
val data = new String(buffer, charset.nioCharset)
val result = data.split("&").map(pair => {
pair.split("=").map(URLDecoder.decode(_, "UTF8")) match {
case Array(key, value) => (key, if (value.isEmpty) None else
Some(value))
}
}).toMap
Right(result)
}
}
}

On 27 jul, 17:57, Mathias <math...@spray.cc> wrote:
> The "parameters" directive only works on query parameters.
> If you'd like to unmarshall the request entity body you'll have to use the "content" directive.
>
> Btw. we will release spray 0.7.0 incl. a vastly updated documentation today.
> This should hopefully clear up things even more...
>
> Cheers,
> Mathias
>
> ---
> math...@spray.cchttp://www.spray.cc

Dirk Louwers

unread,
Jul 28, 2011, 11:31:40 AM7/28/11
to spray-user
Changed it a little realizing that Map.get already supplies an Option
and that value is always at least "" for an existing key:

trait PostUnmarshaller {

implicit object StringMapUnmarshaller extends
UnmarshallerBase[Map[String, String]] {
val canUnmarshalFrom = ContentTypeRange(`application/x-www-form-
urlencoded`) :: Nil

def unmarshal(content: HttpContent) = {
val charset = content.contentType.charset.getOrElse(`UTF-8`)
var buffer: Array[Byte] = new Array[Byte](content.length)
content.inputStream.read(buffer)
val data = new String(buffer, charset.nioCharset)
val result = data.split("&").map(pair => {
pair.split("=").map(URLDecoder.decode(_, "UTF8")) match {
case Array(key, value) if !value.trim.isEmpty => (key,
value)
}
}).toMap
Right(result)
}
}
}

and a basic test:

class PostUnmarshallerSpec extends Specification {
"PostUnmarshaller" should {
"currectly unmarshal an application/x-www-form-urlencoded string"
in {
object Unmarshaller extends PostUnmarshaller
val contentType = ContentType(`application/x-www-form-
urlencoded`)
val buffer = "email=test
%40there.com&password=pwd&passwordConfirm=pwd&username=dirk".getBytes("UTF8")
val content = HttpContent(contentType, buffer)
Unmarshaller.StringMapUnmarshaller.unmarshal(content) must
beLike {
case Right(m) => {
if (m.getOrElse("email", "") != "te...@there.com") false
else if (m.getOrElse("username", "") != "dirk") false
else if (m.getOrElse("password", "") != "pwd") false
else if (m.getOrElse("passwordConfirm", "") != "pwd") false
else true

Mathias

unread,
Jul 29, 2011, 5:42:18 AM7/29/11
to spray...@googlegroups.com
Thanks, Dirk!

I took up on your implementation, added a corresponding Marshaller and committed it together with some more tests:
https://github.com/spray/spray/commit/7a17c8d735ee0398f9174b650526d1a51ef5c3ec

A couple of comments:
- When writing your own (un)marshaller it's usually best to rely on the existing ones for improved DRYness. In this case for example the StringUnmarshaller and StringMarshaller can do the to and from String conversion for us, no need to work on the buffer level ourselves.
- We can use the 'collection.breakOut' helper to save one list copy operation and have the 'map' operationbuild the required Map directly.
- The UnmarshallerBase comes with a handy 'protect' method that takes care of exception handling
- For application-level code it is no problem to have (Un)marshallers for generic things like Map[String, String]. However, at the library level its better to bind the (Un)marshallers to something more specific, since users might want to write (Un)marshallers for Map[String, String] that convert to and from some other format (e.g. a JSON or XML map representation). In order to enable that without implicits collisions I wrapped the map in a one-element case class "FormContent".

Anyway, the current spray 0.8.0-SNAPSHOT build now supports marshalling and unmarshalling "application/x-www-form-encoded" content.

Thanks again and cheers,
Mathias

---
mat...@spray.cc
http://www.spray.cc

Reply all
Reply to author
Forward
0 new messages