Skip to content

Commit

Permalink
Merge pull request #700 from guymers/scala-js-dom-file
Browse files Browse the repository at this point in the history
Use scala-js-dom `File` type
  • Loading branch information
adamw authored Sep 22, 2020
2 parents 075c4f1 + 01e5dfb commit a295169
Show file tree
Hide file tree
Showing 21 changed files with 107 additions and 152 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,14 @@ class AkkaHttpBackend private (
Future
.fromTry(akkaWebsocketRequest)
.flatMap(request => http.singleWebsocketRequest(request, handler, connectionSettings(r).connectionSettings))
.flatMap {
case (wsResponse, wsResult) =>
responseFromAkka(r, wsResponse.response).map { r =>
if (r.code != StatusCode.SwitchingProtocols) {
throw new NotAWebsocketException(r)
} else {
client.ws.WebSocketResponse(Headers(r.headers), wsResult)
}
.flatMap { case (wsResponse, wsResult) =>
responseFromAkka(r, wsResponse.response).map { r =>
if (r.code != StatusCode.SwitchingProtocols) {
throw new NotAWebsocketException(r)
} else {
client.ws.WebSocketResponse(Headers(r.headers), wsResult)
}
}
}
}

Expand Down Expand Up @@ -226,12 +225,12 @@ class AkkaHttpBackend private (
.filterNot(isContentType)
.filterNot(isContentLength)
.map(h => HttpHeader.parse(h.name, h.value))
val errors = parsed.collect {
case ParsingResult.Error(e) => e
val errors = parsed.collect { case ParsingResult.Error(e) =>
e
}
if (errors.isEmpty) {
val headers = parsed.collect {
case ParsingResult.Ok(h, _) => h
val headers = parsed.collect { case ParsingResult.Ok(h, _) =>
h
}

Success(headers.toList)
Expand Down Expand Up @@ -381,8 +380,8 @@ class AkkaHttpBackend private (
object AkkaHttpBackend {
type EncodingHandler = PartialFunction[(HttpResponse, HttpEncoding), HttpResponse]
object EncodingHandler {
def apply(f: (HttpResponse, HttpEncoding) => HttpResponse): EncodingHandler = {
case (body, encoding) => f(body, encoding)
def apply(f: (HttpResponse, HttpEncoding) => HttpResponse): EncodingHandler = { case (body, encoding) =>
f(body, encoding)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@ class DigestAuthenticationBackend[F[_], S, WS_HANDLER[_]](
.flatMap { firstResponse =>
handleResponse(request, firstResponse, ProxyDigestAuthTag, DigestAuthenticator.proxy(_, clientNonceGenerator))
}
.flatMap {
case (secondResponse, proxyAuthHeader) =>
handleResponse(
proxyAuthHeader.map(h => request.header(h)).getOrElse(request),
secondResponse,
DigestAuthTag,
DigestAuthenticator.apply(_, clientNonceGenerator)
).map(_._1)
.flatMap { case (secondResponse, proxyAuthHeader) =>
handleResponse(
proxyAuthHeader.map(h => request.header(h)).getOrElse(request),
secondResponse,
DigestAuthTag,
DigestAuthenticator.apply(_, clientNonceGenerator)
).map(_._1)
}
}

Expand Down
7 changes: 3 additions & 4 deletions core/src/main/scala/sttp/client/RequestBody.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,9 @@ case class MultipartBody(parts: Seq[Part[BasicRequestBody]]) extends RequestBody
object RequestBody {
private[client] def paramsToStringBody(fs: Seq[(String, String)], encoding: String): StringBody = {
val b = fs
.map {
case (key, value) =>
UriCompatibility.encodeQuery(key, encoding) + "=" +
UriCompatibility.encodeQuery(value, encoding)
.map { case (key, value) =>
UriCompatibility.encodeQuery(key, encoding) + "=" +
UriCompatibility.encodeQuery(value, encoding)
}
.mkString("&")

Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/sttp/client/ResponseAs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ object ResponseAs {
def deserializeRightWithError[E: ShowError, T](
doDeserialize: String => Either[E, T]
): (Either[String, String], ResponseMetadata) => Either[ResponseError[E], T] = {
case (Left(s), meta) => Left(HttpError(s, meta.code))
case (Right(s), _) => deserializeWithError(doDeserialize)(implicitly[ShowError[E]])(s)
case (Left(s), meta) => Left(HttpError(s, meta.code))
case (Right(s), _) => deserializeWithError(doDeserialize)(implicitly[ShowError[E]])(s)
}

/**
Expand Down
4 changes: 2 additions & 2 deletions core/src/main/scala/sttp/client/SttpClientException.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ object SttpClientException {
def adjustExceptions[F[_], T](
monadError: MonadError[F]
)(t: => F[T])(usingFn: Exception => Option[Exception]): F[T] = {
monadError.handleError(t) {
case e: Exception => monadError.error(usingFn(e).getOrElse(e))
monadError.handleError(t) { case e: Exception =>
monadError.error(usingFn(e).getOrElse(e))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class ToCurlConverter[R <: RequestT[Identity, _, _]] {
r.headers
// filtering out compression headers so that the results are human-readable, if possible
.filterNot(_.name.equalsIgnoreCase(HeaderNames.AcceptEncoding))
.collect {
case Header(k, v) => s"""-H '$k: $v'"""
.collect { case Header(k, v) =>
s"""-H '$k: $v'"""
}
.mkString(" ")
}
Expand Down
10 changes: 4 additions & 6 deletions core/src/main/scala/sttp/client/listener/ListenerBackend.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ class ListenerBackend[F[_], S, WS_HANDLER[_], L](
override def send[T](request: Request[T, S]): F[Response[T]] = {
listener.beforeRequest(request).flatMap { t =>
responseMonad
.handleError(delegate.send(request)) {
case e: Exception =>
listener.requestException(request, t, e).flatMap(_ => responseMonad.error(e))
.handleError(delegate.send(request)) { case e: Exception =>
listener.requestException(request, t, e).flatMap(_ => responseMonad.error(e))
}
.flatMap { response => listener.requestSuccessful(request, response, t).map(_ => response) }
}
Expand All @@ -29,9 +28,8 @@ class ListenerBackend[F[_], S, WS_HANDLER[_], L](
): F[WebSocketResponse[WS_RESULT]] = {
listener.beforeWebsocket(request).flatMap { t =>
responseMonad
.handleError(delegate.openWebsocket(request, handler)) {
case e: Exception =>
listener.websocketException(request, t, e).flatMap(_ => responseMonad.error(e))
.handleError(delegate.openWebsocket(request, handler)) { case e: Exception =>
listener.websocketException(request, t, e).flatMap(_ => responseMonad.error(e))
}
.flatMap { response => listener.websocketSuccessful(request, response, t).map(_ => response) }
}
Expand Down
30 changes: 7 additions & 23 deletions core/src/main/scalajs/sttp/client/AbstractFetchBackend.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import java.nio.ByteBuffer

import org.scalajs.dom.FormData
import org.scalajs.dom.experimental.{
AbortController,
AbortSignal,
BodyInit,
Fetch,
Expand All @@ -21,7 +22,7 @@ import org.scalajs.dom.experimental.{
Response => FetchResponse
}
import org.scalajs.dom.raw.{Blob, BlobPropertyBag}
import sttp.client.dom.experimental.{AbortController, FilePropertyBag, File => DomFile}
import sttp.client.dom.experimental.{FilePropertyBag, File => DomFile}
import sttp.client.internal.{SttpFile, _}
import sttp.client.monad.MonadError
import sttp.client.monad.syntax._
Expand Down Expand Up @@ -77,48 +78,31 @@ abstract class AbstractFetchBackend[F[_], S](options: FetchOptions, customizeReq
}

val rheaders = new JSHeaders()
request.headers.foreach {
case Header(name, value) =>
rheaders.set(name, value)
request.headers.foreach { case Header(name, value) =>
rheaders.set(name, value)
}

val req = createBody(request.body).map { rbody =>
// use manual so we can return a specific error instead of the generic "TypeError: Failed to fetch"
val rredirect = if (request.options.followRedirects) RequestRedirect.follow else RequestRedirect.manual
val rsignal = signal.orUndefined

val requestInitStatic = new RequestInit() {
val requestInit = new RequestInit {
var method: js.UndefOr[HttpMethod] = request.method.method.asInstanceOf[HttpMethod]

var headers: js.UndefOr[HeadersInit] = rheaders

var body: js.UndefOr[BodyInit] = rbody

var referrer: js.UndefOr[String] = js.undefined

var referrerPolicy: js.UndefOr[ReferrerPolicy] = js.undefined

var mode: js.UndefOr[RequestMode] = options.mode.orUndefined

var credentials: js.UndefOr[RequestCredentials] = options.credentials.orUndefined

var cache: js.UndefOr[RequestCache] = js.undefined

var redirect: js.UndefOr[RequestRedirect] = rredirect

var integrity: js.UndefOr[String] = js.undefined

var keepalive: js.UndefOr[Boolean] = js.undefined

var signal: js.UndefOr[AbortSignal] = js.undefined

var signal: js.UndefOr[AbortSignal] = rsignal
var window: js.UndefOr[Null] = js.undefined
}

val requestInitDynamic = requestInitStatic.asInstanceOf[js.Dynamic]
signal.foreach(s => requestInitDynamic.updateDynamic("signal")(s))
requestInitDynamic.updateDynamic("redirect")(rredirect) // named wrong in RequestInit
val requestInit = requestInitDynamic.asInstanceOf[RequestInit]

new FetchRequest(request.uri.toString, requestInit)
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scalajs/sttp/client/SttpExtensions.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package sttp.client

import sttp.client.dom.experimental.File
import org.scalajs.dom.File
import sttp.client.internal.SttpFile
import sttp.model.Part

Expand Down

This file was deleted.

6 changes: 2 additions & 4 deletions core/src/main/scalajs/sttp/client/dom/experimental/File.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package sttp.client.dom.experimental

import org.scalajs.dom.raw.Blob

import scala.scalajs.js
import scala.scalajs.js.annotation.JSGlobal

Expand All @@ -12,9 +10,9 @@ import scala.scalajs.js.annotation.JSGlobal
@JSGlobal
class File(
parts: js.Array[js.Any] = js.native,
val name: String = js.native,
override val name: String = js.native,
options: FilePropertyBag = js.native
) extends Blob {
) extends org.scalajs.dom.File {
val lastModified: Int = js.native
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package sttp.client.internal

import sttp.client.dom.experimental.{File => DomFile}
import sttp.client.dom.experimental.File
import org.scalajs.dom.File

// wrap a DomFile
trait SttpFileExtensions { self: SttpFile =>
Expand Down
46 changes: 22 additions & 24 deletions core/src/main/scalajvm/sttp/client/HttpURLConnectionBackend.scala
Original file line number Diff line number Diff line change
Expand Up @@ -170,20 +170,19 @@ class HttpURLConnectionBackend private (

// https://stackoverflow.com/questions/31406022/how-is-an-http-multipart-content-length-header-value-calculated
val contentLength = partsWithHeaders
.map {
case (headers, p) =>
val bodyLen: Option[Long] = p.body match {
case StringBody(b, encoding, _) =>
Some(b.getBytes(encoding).length.toLong)
case ByteArrayBody(b, _) => Some(b.length.toLong)
case ByteBufferBody(_, _) => None
case InputStreamBody(_, _) => None
case FileBody(b, _) => Some(b.toFile.length())
}

val headersLen = headers.getBytes(Iso88591).length

bodyLen.map(bl => dashesLen + boundaryLen + crLfLen + headersLen + crLfLen + crLfLen + bl + crLfLen)
.map { case (headers, p) =>
val bodyLen: Option[Long] = p.body match {
case StringBody(b, encoding, _) =>
Some(b.getBytes(encoding).length.toLong)
case ByteArrayBody(b, _) => Some(b.length.toLong)
case ByteBufferBody(_, _) => None
case InputStreamBody(_, _) => None
case FileBody(b, _) => Some(b.toFile.length())
}

val headersLen = headers.getBytes(Iso88591).length

bodyLen.map(bl => dashesLen + boundaryLen + crLfLen + headersLen + crLfLen + crLfLen + bl + crLfLen)
}
.foldLeft(Option(finalBoundaryLen)) {
case (Some(acc), Some(l)) => Some(acc + l)
Expand All @@ -206,16 +205,15 @@ class HttpURLConnectionBackend private (
total += s.getBytes(Iso88591).length.toLong
}

partsWithHeaders.foreach {
case (headers, p) =>
writeMeta(dashes)
writeMeta(boundary)
writeMeta(CrLf)
writeMeta(headers)
writeMeta(CrLf)
writeMeta(CrLf)
writeBasicBody(p.body, os)
writeMeta(CrLf)
partsWithHeaders.foreach { case (headers, p) =>
writeMeta(dashes)
writeMeta(boundary)
writeMeta(CrLf)
writeMeta(headers)
writeMeta(CrLf)
writeMeta(CrLf)
writeBasicBody(p.body, os)
writeMeta(CrLf)
}

// final boundary
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package sttp.client.testing

import org.scalajs.dom.FileReader
import org.scalajs.dom.{Blob, FileReader}
import org.scalajs.dom.raw.{Event, UIEvent}
import sttp.client._
import sttp.client.dom.experimental.{FilePropertyBag, File => DomFile}
import sttp.client.dom.experimental.{FilePropertyBag, File => DomFileWithBody}
import sttp.client.internal.SparkMD5

import scala.concurrent.{Future, Promise}
Expand All @@ -16,9 +16,9 @@ import HttpTest.endpoint

trait HttpTestExtensions[F[_]] extends AsyncExecutionContext { self: HttpTest[F] =>

private def withTemporaryFile[T](content: Option[Array[Byte]])(f: DomFile => Future[T]): Future[T] = {
private def withTemporaryFile[T](content: Option[Array[Byte]])(f: DomFileWithBody => Future[T]): Future[T] = {
val data = content.getOrElse(Array.empty)
val file = new DomFile(
val file = new DomFileWithBody(
Array(data.toTypedArray.asInstanceOf[js.Any]).toJSArray,
"temp.txt",
FilePropertyBag(
Expand All @@ -28,9 +28,9 @@ trait HttpTestExtensions[F[_]] extends AsyncExecutionContext { self: HttpTest[F]
f(file)
}

private def withTemporaryNonExistentFile[T](f: DomFile => Future[T]): Future[T] = withTemporaryFile(None)(f)
private def withTemporaryNonExistentFile[T](f: DomFileWithBody => Future[T]): Future[T] = withTemporaryFile(None)(f)

private def md5FileHash(file: DomFile): Future[String] = {
private def md5Hash(blob: Blob): Future[String] = {
val p = Promise[String]()

val fileReader = new FileReader()
Expand All @@ -41,7 +41,7 @@ trait HttpTestExtensions[F[_]] extends AsyncExecutionContext { self: HttpTest[F]
}
fileReader.onerror = (_: Event) => p.failure(JavaScriptException("Error reading file"))
fileReader.onabort = (_: Event) => p.failure(JavaScriptException("File read aborted"))
fileReader.readAsArrayBuffer(file)
fileReader.readAsArrayBuffer(blob)

p.future
}
Expand All @@ -61,7 +61,7 @@ trait HttpTestExtensions[F[_]] extends AsyncExecutionContext { self: HttpTest[F]
withTemporaryNonExistentFile { file =>
val req = basicRequest.get(uri"$endpoint/download/binary").response(asFile(file))
req.send().toFuture().flatMap { resp =>
md5FileHash(resp.body.right.get).map { _ shouldBe binaryFileMD5Hash }
md5Hash(resp.body.right.get).map { _ shouldBe binaryFileMD5Hash }
}
}
}
Expand All @@ -70,7 +70,7 @@ trait HttpTestExtensions[F[_]] extends AsyncExecutionContext { self: HttpTest[F]
withTemporaryNonExistentFile { file =>
val req = basicRequest.get(uri"$endpoint/download/text").response(asFile(file))
req.send().toFuture().flatMap { resp =>
md5FileHash(resp.body.right.get).map { _ shouldBe textFileMD5Hash }
md5Hash(resp.body.right.get).map { _ shouldBe textFileMD5Hash }
}
}
}
Expand Down
Loading

0 comments on commit a295169

Please sign in to comment.