Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2014–2017 SlamData Inc.
*
* 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 the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package quasar.api
import slamdata.Predef._
import quasar.contrib.scalaz.catchable._
import quasar.contrib.scalaz.eitherT._
import quasar.effect.Failure
import quasar.fp._
import argonaut._, Argonaut._
import monocle._, macros.Lenses
import org.http4s._
import org.http4s.headers._
import org.http4s.dsl._
import scalaz.{Optional => _, _}
import scalaz.std.iterable._
import scalaz.Scalaz._
import scalaz.concurrent.Task
import scalaz.stream.Process
import scodec.bits.ByteVector
@Lenses
final case class QResponse[S[_]](status: Status, headers: Headers, body: Process[Free[S, ?], ByteVector]) {
import QResponse.HttpResponseStreamFailureException
def flatMapS[T[_]](f: S ~> Free[T, ?]): QResponse[T] =
copy[T](body = body.translate[Free[T, ?]](free.flatMapSNT(f)))
def mapS[T[_]](f: S ~> T): QResponse[T] =
copy[T](body = body.translate[Free[T, ?]](free.mapSNT(f)))
def translate[T[_]](f: Free[S, ?] ~> Free[T, ?]): QResponse[T] =
copy[T](body = body.translate(f))
def modifyHeaders(f: Headers => Headers): QResponse[S] =
QResponse.headers.modify(f)(this)
def toHttpResponse(i: S ~> ResponseOr): Response =
toHttpResponseF(free.foldMapNT(i))
def toHttpResponseF(i: Free[S, ?] ~> ResponseOr): Response = {
val failTask: ResponseOr ~> Task = new (ResponseOr ~> Task) {
def apply[A](ror: ResponseOr[A]) =
ror.fold(resp => Task.fail(new HttpResponseStreamFailureException(resp)), _.point[Task]).join
}
def handleBytes(bytes: Process[ResponseOr, ByteVector]): Response =
Response(body = bytes.translate(failTask))
.withStatus(status)
.putHeaders(headers.toList: _*)
handleBytes(body.translate[ResponseOr](i))
}
def withStatus(s: Status): QResponse[S] =
QResponse.status.set(s)(this)
}
object QResponse {
final class HttpResponseStreamFailureException(alternate: Response)
extends java.lang.Exception
def empty[S[_]]: QResponse[S] =
QResponse(NoContent, Headers.empty, Process.halt)
def ok[S[_]]: QResponse[S] =
empty[S].withStatus(Ok)
def header[S[_]](key: HeaderKey.Extractable): Optional[QResponse[S], key.HeaderT] =
Optional[QResponse[S], key.HeaderT](
qr => qr.headers.get(key))(
h => _.modifyHeaders(_.put(h)))
def json[A: EncodeJson, S[_]](status: Status, a: A): QResponse[S] =
string[S](status, a.asJson.pretty(minspace)).modifyHeaders(_.put(
`Content-Type`(MediaType.`application/json`, Some(Charset.`UTF-8`))))
def response[S[_], A]
(status: Status, a: A)
(implicit E: EntityEncoder[A], S0: Task :<: S)
: QResponse[S] =
QResponse(
status,
E.headers,
Process.await(E.toEntity(a))(_.body).translate[Free[S, ?]](free.injectFT))
/**
* This QResponse constructor attempts to eliminate all effects in this
* stream that aren't actually part of the response stream by eagerly evaluating
* the "first few" effects of the stream. Examples of such effects are
* opening a quasar filesystem file, initiating a query to a connector, etc. Doing so
* allows us to provide a proper error response to clients instead of sending a
* Http OK and then failing to stream the response body because actually it
* turned out the file didn't exist after all.
*
* The better fix for this problem would probably be to avoid constructing
* such processes (at least for use in the web layer) and instead make a
* distinction between the effects that create a stream and the effects
* that "run" the stream.
*
* In the meantime, this constructor should mostly do the right thing if
* provided with such processes.
*/
@SuppressWarnings(Array("org.wartremover.warts.Overloading"))
def streaming[S[_], A]
(p: Process[Free[S, ?], A])
(implicit E: EntityEncoder[A], S0: Task :<: S)
: Free[S, QResponse[S]] = {
val responseBody = p.flatMap[Free[S, ?], ByteVector](a =>
Process.await(E.toEntity(a))(_.body).translate[Free[S, ?]](free.injectFT))
/** Producing this many bytes from a `Process[F, ByteVector]` should require
* at least one `F` effect.
*
* The scenarios this is intended to handle involve prepending a small wrapper
* around a stream, like "[\n" when outputting JSON data in an array, and thus
* 100 bytes seemed large enough to contain these cases and small enough as to
* not force more than is needed.
*
* We cannot just `Process.unemit` the `Process` as there may be non-`F` `Await`
* steps encountered before an actual `F` effect (from many of the combinators
* in `process1` and the like).
*
* This leads us to the current workaround which is to define this threshold
* which should be, ideally, just large enough to require the first `F` to
* produce the bytes, but not more. We then consume the byte stream until it
* ends or we've consumed this many bytes. Finally we have a chance to inspect
* the `F` and see if anything failed before handing the rest of the process
* to http4s to continue streaming to the client as normal.
*/
val PROCESS_EFFECT_THRESHOLD_BYTES = 100L
guardStream(responseBody, PROCESS_EFFECT_THRESHOLD_BYTES).map(guardedStream =>
QResponse(
Ok,
E.headers,
guardedStream))
}
def streaming[S[_], A, E]
(p: Process[EitherT[Free[S, ?], E, ?], A])
(implicit A: EntityEncoder[A], S0: Task :<: S, S1: Failure[E, ?] :<: S)
: Free[S, QResponse[S]] = {
val failure = Failure.Ops[E, S]
streaming(p.translate(failure.unattemptT))
}
def string[S[_]](status: Status, s: String): QResponse[S] =
QResponse(
status,
Headers(`Content-Type`(MediaType.`text/plain`, Some(Charset.`UTF-8`))),
Process.emit(ByteVector.view(s.getBytes(Charset.`UTF-8`.nioCharset))))
/**
* If any errors were to occur before lookAhead bytes would be read from `stream`, these errors will
* now happen whenever the outer `M` is first evaluated.
*/
private def guardStream[M[_]: Catchable: Monad](stream: Process[M, ByteVector], lookAhead: Long): M[Process[M, ByteVector]] =
stream.stepUntil(_.foldMap(_.length) >= lookAhead)
}