All Downloads are FREE. Search and download functionalities are using the official Maven repository.

korolev.server.internal.services.PostService.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017-2020 Aleksey Fomkin
 *
 * 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 korolev.server.internal.services

import korolev.Qsid
import korolev.data.Bytes
import korolev.effect.{Effect, Reporter, Stream}
import korolev.effect.syntax._
import korolev.internal.Frontend.DownloadFileMeta
import korolev.server.HttpResponse
import korolev.server.internal.{FormDataCodec, HttpResponse}
import korolev.web.{Headers, Response}
import scala.concurrent.ExecutionContext

private[korolev] final class PostService[F[_]: Effect](
  reporter: Reporter,
  sessionsService: SessionsService[F, _, _],
  commonService: CommonService[F],
  formDataCodec: FormDataCodec
)(implicit ec: ExecutionContext) {

  def formData(
    qsid: Qsid,
    descriptor: String,
    headers: Seq[(String, String)],
    data: Stream[F, Bytes]
  ): F[HttpResponse[F]] = {

    def extractBoundary() = headers.collectFirst {
      case (k, v) if k.toLowerCase == "content-type" && v.contains("multipart/form-data") => v
    }.flatMap {
      _.split(';').toList
        .filter(_.contains('='))
        .map(_.split('=').toList.map(_.trim))
        .collectFirst { case "boundary" :: s :: Nil => s }
    }
      .fold(Effect[F].fail[String](new Exception("Content-Type should be `multipart/form-data`")))(Effect[F].pure)

    def parseFormData(formBytes: Bytes, boundary: String) = Effect[F].fork {
      Effect[F].delay {
        try {
          Right(formDataCodec.decode(formBytes.asBuffer, boundary))
        } catch {
          case error: Throwable =>
            Left(error)
        }
      }
    }

    sessionsService.getApp(qsid) flatMap {
      case Some(app) =>
        for {
          formBytes       <- data.fold(Bytes.empty)(_ ++ _)
          boundary        <- extractBoundary()
          errorOrFormData <- parseFormData(formBytes, boundary)
          _               <- app.frontend.resolveFormData(descriptor, errorOrFormData)
        } yield {
          commonService.simpleOkResponse
        }
      case None =>
        commonService.badRequest(ErrorSessionNotFound(qsid))
    }
  }

  def filesInfo(qsid: Qsid, descriptor: String, body: Stream[F, Bytes]): F[HttpResponse[F]] = {
    def parseFilesInfo(message: String) = message
      .split("\n")
      .toList
      .map { entry =>
        val slash = entry.lastIndexOf('/')
        (entry.substring(0, slash), entry.substring(slash + 1).toLong)
      }

    sessionsService.getApp(qsid) flatMap {
      case Some(app) =>
        for {
          message <- body.fold(Bytes.empty)(_ ++ _).map(_.asUtf8String) // file_name/size_in_bytes\n
          sizes = {
            if (message.isEmpty) List.empty[(String, Long)]
            else parseFilesInfo(message)
          }
          _ <- app.frontend.resolveFileNames(descriptor, sizes)
        } yield commonService.simpleOkResponse
      case None =>
        commonService.badRequest(ErrorSessionNotFound(qsid))
    }
  }

  def downloadFile(qsid: Qsid, descriptor: String): F[HttpResponse[F]] =
    sessionsService.getApp(qsid) flatMap {
      case Some(app) =>
        app.frontend.resolveFileDownload(descriptor).flatMap {
          case Some(DownloadFileMeta(stream, maybeLength, mimeType)) =>
            val headers =
              (Headers.ContentType -> mimeType) ::
                ("Accept-Ranges"   -> "none") :: Nil
            val response = Response(Response.Status.Ok, stream, headers, maybeLength)
            Effect[F].pure(response)
          case None => commonService.notFoundResponseF
        }
      case None => commonService.badRequest(ErrorSessionNotFound(qsid))
    }

  def uploadFile(
    qsid: Qsid,
    descriptor: String,
    headers: Seq[(String, String)],
    body: Stream[F, Bytes]
  ): F[HttpResponse[F]] = {
    val (consumed, chunks) = body.handleConsumed
    sessionsService.getApp(qsid) flatMap {
      case Some(app) =>
        for {
          _ <- app.frontend.resolveFile(descriptor, chunks)
          // Do not response until chunks are not
          // consumed inside an application
          _ <- consumed
        } yield commonService.simpleOkResponse
      case None =>
        commonService.badRequest(ErrorSessionNotFound(qsid))
    }
  }

  private def ErrorSessionNotFound(qsid: Qsid) =
    s"There is no app instance matched to $qsid"
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy