1

I am looking to get a better understanding of Spray. This is in the context of sending a file as multipart data.

The code that was suggested to me is :

import akka.actor.ActorSystem
import spray.client.pipelining._
import spray.http.{MediaTypes, BodyPart, MultipartFormData}

object UploadFileExample extends App {
  implicit val system = ActorSystem("simple-spray-client")
  import system.dispatcher // execution context for futures below

  val pipeline = sendReceive
  val payload = MultipartFormData(Seq(BodyPart(new File("/tmp/test.pdf"), "datafile", MediaTypes.`application/pdf`)))
  val request =
    Post("http://localhost:8080/file-upload", payload)

  pipeline(request).onComplete { res =>
    println(res)
    system.shutdown()
  }
}

Which is fine and works of course.

However I want to understand what is under the hood so I can do things by myself.

Here are the confusion coming from this code:

BodyPart(new File("/tmp/test.pdf"), "datafile", MediaTypes.`application/pdf`)

This is the first issue, indeed, BodyPart only has one apply method that is closely match:

def apply(file: File, fieldName: String, contentType: ContentType): BodyPart = 
 apply(HttpEntity(contentType, HttpData(file)), fieldName, Map.empty.updated("filename", file.getName)) 

It takes a contentType and not a MediaType.

However I found in contentType

object ContentType { 
  
  private[http] case object `; charset=` extends SingletonValueRenderable 
  
  def apply(mediaType: MediaType, charset: HttpCharset): ContentType = apply(mediaType, Some(chars  et)) 

  implicit def apply(mediaType: MediaType): ContentType = apply(mediaType, None) }

But ContentType is not in the scope of the Main (see first code at the top). Where does that implicit conversion come from?

The last thing that I do not understand here is:

val payload = MultipartFormData(Seq(BodyPart(new File("/tmp/test.pdf"),
"datafile", MediaTypes.`application/pdf`)))
Post("http://localhost:8080/file-upload", payload)

The problem here is that, it is based on the RequestBuilding (as can be found in the RequestBuilding Source)

val Post = new RequestBuilder(POST)

with an object that contain the apply method:

def apply[T: Marshaller](uri: String, content: Option[T]): HttpRequest = apply(Uri(uri), content)
....
....
def apply[T: Marshaller](uri: Uri, content: Option[T]): HttpRequest = {
      val ctx = new CollectingMarshallingContext {
        override def startChunkedMessage(entity: HttpEntity, ack: Option[Any],
                                         headers: Seq[HttpHeader])(implicit sender: ActorRef) =
          sys.error("RequestBuilding with marshallers producing chunked requests is not supported")
      }
      content match {
        case None ⇒ HttpRequest(method, uri)
        case Some(value) ⇒ marshalToEntityAndHeaders(value, ctx) match {
          case Right((entity, headers)) ⇒ HttpRequest(method, uri, headers.toList, entity)
          case Left(error)              ⇒ throw error
        }
      }
    }

MultiPartFormData is not a Marshaler, hence I do not understand how it works.

How can I figure that out?

halfer
  • 19,824
  • 17
  • 99
  • 186
MaatDeamon
  • 9,532
  • 9
  • 60
  • 127
  • The answer to this may help you out: http://stackoverflow.com/questions/3179415/is-there-a-systematic-way-to-discover-which-implicit-defs-are-in-scope-and-whic – childofsoong Feb 11 '15 at 23:09
  • Yes indeed, better this one tho https://confluence.jetbrains.com/display/IntelliJIDEA/Working+with+Scala+Implicit+Conversions – MaatDeamon Feb 12 '15 at 00:55
  • 1
    In any case, it sounds like i need a lesson into Type Class combined with implicit. Let's take to 2 to 3 days, to get some solid ground on those and never be bother or mystified again ;) – MaatDeamon Feb 12 '15 at 00:56

0 Answers0