Skip to content
This repository has been archived by the owner on Apr 24, 2024. It is now read-only.

Commit

Permalink
merge with master
Browse files Browse the repository at this point in the history
Conflicts:
	examples/spray-can/simple-http-server/src/main/scala/spray/examples/FileUploadHandler.scala
	spray-httpx/src/main/scala/spray/httpx/unmarshalling/MultipartUnmarshallers.scala
	spray-httpx/src/test/scala/spray/httpx/unmarshalling/FormDataUnmarshallersSpec.scala
  • Loading branch information
jrudolph committed Sep 26, 2013
2 parents ad593d1 + f4a92f0 commit 0339833
Show file tree
Hide file tree
Showing 350 changed files with 4,586 additions and 2,258 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
This software is licensed under the Apache 2 license, quoted below.

Copyright (C) 2011-2013 spray.io
Copyright © 2011-2013 the spray project <http://spray.io>

Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy of
Expand Down
3 changes: 2 additions & 1 deletion docs/_sphinx/exts/includecode.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ def run(self):
array = line.split("//", 1)
l = array[0].rstrip() if array[1].startswith("/") > 0 else array[0].strip()
res.append(l + '\n')
elif re.search("def "+re.escape(snippet)+"(?=[:(\[])", line):
elif re.search("(def|val) "+re.escape(snippet)+"(?=[:(\[])", line):
# match signature line from `def <snippet>` but without trailing `=`
start = line.find("def")
if start == -1: start = line.find("val")
end = line.rfind("=")
res.append(line[start:end] + '\n')

Expand Down
8 changes: 4 additions & 4 deletions docs/documentation/spray-caching/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ both featuring last-recently-used cache eviction semantics and both internally w
They difference between the two only consists of whether they support time-based entry expiration or not.

The easiest way to construct a cache instance is via the ``apply`` method of the ``LruCache`` object, which has the
following signature and creates a new ``ExpiringLruCache`` or ``SimpleLruCache`` depending on whether a non-zero and
finite ``timeToLive`` and/or ``timeToIdle`` is set or not:
following signature and creates a new ``ExpiringLruCache`` or ``SimpleLruCache`` depending on whether
``timeToLive`` and/or ``timeToIdle`` are finite (= expiring) or infinite:

.. includecode:: /../spray-caching/src/main/scala/spray/caching/LruCache.scala
:snippet: source-quote-LruCache-apply
Expand All @@ -97,7 +97,7 @@ ExpiringLruCache
This implementation has the same limited capacity behavior as the ``SimpleLruCache`` but in addition supports
time-to-live as well as time-to-idle expiration.
The former provides an upper limit to the time period an entry is allowed to remain in the cache while the latter
limits the maximum time an entry is kept without having been accessed. If both values are non-zero the time-to-live
limits the maximum time an entry is kept without having been accessed. If both values are finite the time-to-live
has to be strictly greater than the time-to-idle.

.. note:: Expired entries are only evicted upon next access (or by being thrown out by the capacity constraint), so
Expand All @@ -106,4 +106,4 @@ has to be strictly greater than the time-to-idle.

.. _Cache: https://github.com/spray/spray/blob/master/spray-caching/src/main/scala/spray/caching/Cache.scala
.. _SimpleLruCache and ExpiringLruCache: https://github.com/spray/spray/blob/master/spray-caching/src/main/scala/spray/caching/LruCache.scala
.. _concurrentlinkedhashmap: http://code.google.com/p/concurrentlinkedhashmap/
.. _concurrentlinkedhashmap: http://code.google.com/p/concurrentlinkedhashmap/
4 changes: 2 additions & 2 deletions docs/documentation/spray-can/http-client/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ headers.
Additionally *spray-can* will render a

- ``Host`` request header if none is explicitly added.
- ``User-Agent`` header if one is configured. If none is configured an explicitly added added ``User-Agent`` header is
also rendered.
- ``User-Agent`` default request header if none is explicitly defined. The default value can be configured with the
``spray.can.client.user-agent-header`` configuration setting.

.. note:: The ``Content-Type`` header has special status in *spray* since its value is part of the ``HttpEntity`` model
class. Even though the header also remains in the ``headers`` list of the ``HttpResponse`` *sprays* higher layers
Expand Down
2 changes: 1 addition & 1 deletion docs/documentation/spray-http/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Content-Type Header

One thing worth highlighting is the special treatment of the HTTP ``Content-Type`` header. Since the binary content of
HTTP message entities can only be properly interpreted when the corresponding content-type is known *spray-http* puts
the content-type value very close to the entity data. The ``HttpBody`` type (the non-empty variant of the
the content-type value very close to the entity data. The ``HttpEntity.NonEmpty`` type (the non-empty variant of the
``HttpEntity``) is essentially little more than a tuple of the ``ContentType`` and the entity's bytes.
All logic in *spray* that needs to access the content-type of an HTTP message always works with the ``ContentType``
value in the ``HttpEntity``. Potentially existing instances of the ``Content-Type`` header in the ``HttpMessage``'s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ class UnmarshallingExamplesSpec extends Specification {
object Person {
implicit val PersonUnmarshaller =
Unmarshaller[Person](`application/vnd.acme.person`) {
case HttpBody(contentType, buffer) =>
case HttpEntity.NonEmpty(contentType, buffer) =>
// unmarshal from the string format used in the marshaller example
val Array(_, name, first, age) =
buffer.asString.split(":,".toCharArray).map(_.trim)
Person(name, first, age.toInt)

// if we had meaningful semantics for the EmptyEntity
// we could add a case for the EmptyEntity:
// case EmptyEntity => ...
// if we had meaningful semantics for the HttpEntity.Empty
// we could add a case for the HttpEntity.Empty:
// case HttpEntity.Empty => ...
}
}
//#
Expand Down Expand Up @@ -56,6 +56,6 @@ class UnmarshallingExamplesSpec extends Specification {
implicit val myNodeSeqUnmarshaller = Unmarshaller.forNonEmpty[NodeSeq]

HttpEntity(MediaTypes.`text/xml`, "<xml>yeah</xml>").as[NodeSeq] === Right(<xml>yeah</xml>)
EmptyEntity.as[NodeSeq] === Left(ContentExpected)
HttpEntity.Empty.as[NodeSeq] === Left(ContentExpected)
}
}
4 changes: 2 additions & 2 deletions docs/documentation/spray-httpx/unmarshalling.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ The relevant sources are:
- Deserializer_
- BasicUnmarshallers_
- MetaUnmarshallers_
- MultipartUnmarshallers_
- FormDataUnmarshallers_

.. _Deserializer: https://github.com/spray/spray/blob/master/spray-httpx/src/main/scala/spray/httpx/unmarshalling/Deserializer.scala
.. _BasicUnmarshallers: https://github.com/spray/spray/blob/master/spray-httpx/src/main/scala/spray/httpx/unmarshalling/BasicUnmarshallers.scala
.. _MetaUnmarshallers: https://github.com/spray/spray/blob/master/spray-httpx/src/main/scala/spray/httpx/unmarshalling/MetaUnmarshallers.scala
.. _MultipartUnmarshallers: https://github.com/spray/spray/blob/master/spray-httpx/src/main/scala/spray/httpx/unmarshalling/MultipartUnmarshallers.scala
.. _FormDataUnmarshallers: https://github.com/spray/spray/blob/master/spray-httpx/src/main/scala/spray/httpx/unmarshalling/FormDataUnmarshallers.scala


Implicit Resolution
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class HttpServiceExamplesSpec extends Specification with Specs2RouteTest {
} ~
post {
// decompresses the request with Gzip or Deflate when required
decompressRequest {
decompressRequest() {
// unmarshal with in-scope unmarshaller
entity(as[Order]) { order =>
// transfer to newly spawned actor
Expand Down Expand Up @@ -126,7 +126,7 @@ class HttpServiceExamplesSpec extends Specification with Specs2RouteTest {
cache(simpleCache) {
// optionally compresses the response with Gzip or Deflate
// if the client accepts compressed responses
compressResponse {
compressResponse() {
// serve up static content from a JAR resource
getFromResourceDirectory("docs")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package docs.directives

import spray.http.StatusCodes._
import scala.concurrent.Future
import scala.util.{Try, Success, Failure}
import spray.util.LoggingContext
import spray.routing.ExceptionHandler
import akka.actor.{Actor, Props}
import akka.pattern.ask
import akka.util.Timeout
import java.util.concurrent.TimeUnit

class FutureDirectivesExamplesSpec extends DirectivesSpec {
object TestException extends Throwable

implicit def myExceptionHandler(implicit log: LoggingContext) =
ExceptionHandler {
case TestException => ctx =>
ctx.complete(InternalServerError, "Unsuccessful future!")
}

def handleResponse(response: Try[String]) = response match {
case Success(value) => complete(value)
case Failure(ex) => failWith(ex)
}

val resourceActor = system.actorOf(Props(new Actor {
def receive = { case _ => sender ! "resource" }
}))
implicit val responseTimeout = Timeout(2, TimeUnit.SECONDS)

"single-execution" in {
val route =
onSuccess((resourceActor ? "GetResource").mapTo[String]) { resource =>
// inner routes will always use the resource computed at creation time.
complete(resource)
}

Get("/") ~> route ~> check {
status === OK
entityAs[String] === "resource"
}
}

"per-request-execution" in {
val route =
dynamic {
onSuccess((resourceActor ? "GetResource").mapTo[String]) { resources =>
// inner routes will get a fresh resource on every execution.
complete(resources)
}
}

Get("/") ~> route ~> check {
status === OK
entityAs[String] === "resource"
}
}

"example-1" in {
val route =
path("success") {
onComplete(Future { "Ok" }) {
handleResponse
}
} ~
path("failure") {
onComplete(Future.failed[String](TestException)) {
handleResponse
}
}

Get("/success") ~> route ~> check {
entityAs[String] === "Ok"
}

Get("/failure") ~> sealRoute(route) ~> check {
status === InternalServerError
entityAs[String] === "Unsuccessful future!"
}
}

"example-2" in {
val route =
path("success") {
onSuccess(Future { "Ok" }) { extraction =>
complete(extraction)
}
} ~
path("failure") {
onSuccess(Future.failed[String](TestException)) { extraction =>
complete(extraction)
}
}


Get("/success") ~> route ~> check {
entityAs[String] === "Ok"
}

Get("/failure") ~> sealRoute(route) ~> check {
status === InternalServerError
entityAs[String] === "Unsuccessful future!"
}
}

"example-3" in {
val route =
path("success") {
onFailure(Future { "Ok" }) { extraction =>
failWith(extraction) // not executed.
}
} ~
path("failure") {
onFailure(Future.failed[String](TestException)) { extraction =>
failWith(extraction)
}
}


Get("/success") ~> route ~> check {
entityAs[String] === "Ok"
}

Get("/failure") ~> sealRoute(route) ~> check {
status === InternalServerError
entityAs[String] === "Unsuccessful future!"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package docs.directives

import spray.http._
import spray.httpx.SprayJsonSupport
import spray.httpx.unmarshalling.Unmarshaller
import spray.json.DefaultJsonProtocol
import HttpHeaders._
import MediaTypes._

//# person-case-class
case class Person(name: String, favoriteNumber: Int)

//# person-json-support
object PersonJsonSupport extends DefaultJsonProtocol with SprayJsonSupport {
implicit val PortofolioFormats = jsonFormat2(Person)
}
//#

class MarshallingDirectivesExamplesSpec extends DirectivesSpec {

"example-entity-with-json" in {
import PersonJsonSupport._

val route = post {
entity(as[Person]) { person =>
complete(s"Person: ${person.name} - favorite number: ${person.favoriteNumber}")
}
}

Post("/", HttpEntity(`application/json`, """{ "name": "Jane", "favoriteNumber" : 42 }""" )) ~>
route ~> check {
entityAs[String] === "Person: Jane - favorite number: 42"
}
}

"example-produce-with-json" in {
import PersonJsonSupport._

val findPerson = (f: Person => Unit) => {

//... some processing logic...

//complete the request
f(Person("Jane", 42))
}

val route = get {
produce(instanceOf[Person]) { completionFunction => ctx => findPerson(completionFunction) }
}

Get("/") ~> route ~> check {
mediaType === `application/json`
entityAs[String] must contain(""""name": "Jane"""")
entityAs[String] must contain(""""favoriteNumber": 42""")
}
}

"example-handleWith-with-json" in {
import PersonJsonSupport._

val updatePerson = (person: Person) => {

//... some processing logic...

//return the person
person
}

val route = post {
handleWith(updatePerson)
}

Post("/", HttpEntity(`application/json`, """{ "name": "Jane", "favoriteNumber" : 42 }""" )) ~>
route ~> check {
mediaType === `application/json`
entityAs[String] must contain(""""name": "Jane"""")
entityAs[String] must contain(""""favoriteNumber": 42""")
}
}
}
Loading

0 comments on commit 0339833

Please sign in to comment.