1

I am uploading a videos and images using web-service and save the images in our application. When i save the files, the files are save on root of application folder. I want to access those images and videos with localhost url, like: I upload the file and save under app-root/upload/image.jpg. In my route mapping file, i declare routing as below:

GET     /uploads/                                   staticDir:/upload 

As define in Play Documentation. But still getting an compile time error: Controller method call expected. I want to access image like this http://localhost:9999/uploads/image.jpg

Harmeet Singh Taara
  • 6,483
  • 20
  • 73
  • 126
  • `GET /uploads/*file controllers.Assets.at(path="/uploads", file)` – sarveshseri Mar 17 '15 at 10:35
  • No @SarveshKumarSingh this is not the way. From this, i got another compilation error. Please join chat root for disucssion. http://stackoverflow.com/questions/11937335/serving-static-public-file-from-play-2-scala-controller – Harmeet Singh Taara Mar 17 '15 at 10:56

1 Answers1

0

Well... One way of doing this is by adding following routes,

GET   /uploads/*file        controllers.Assets.at(path="/uploads", file)

But, it will interfere with the reverse-routing of already existing route which is,

GET     /assets/*file               controllers.Assets.at(path="/public", file)

And then you will have to use your these two assets routes as - @route.Assets.at("public", filename) and @route.Assets.at("uploads", filename) which means all your templates which use you public assets route as - @route.Assets.at(filename) will have to be changed. Which can be a hassle in an existing big project.

You can avoid this by using following method,

Create another controller as, package controllers

object FileServer extends Controller {

  def serveUploadedFiles1 = controllers.Assets.at( dicrectoryPath, file, false )

  // Or... following is same as above

  def serveUploadedFiles2( file: String ) = Action.async {
    implicit request => {
      val dicrectoryPath = "/uploads"
      controllers.Assets.at( dicrectoryPath, file, false ).apply( request )
    }
  }

}

The above should have worked... but seems like play does a lot of meta-data checking on the requested "Assets" which somehow results in empty results for all /uploads/filename requests. I tried to look into the play-source code to check, but it seems like it may take sometime to figure it out.

So I think we can make do with following simpler method ( It can be refined further in so many ways.).

object FileServer extends Controller {

  import play.api.http.ContentTypes
  import play.api.libs.MimeTypes
  import play.api.libs.iteratee.Enumerator
  import play.api.libs.concurrent.Execution.Implicits.defaultContext

  def serveUploadedFiles(file: String) = Action { implicit request =>
    val fileResUri = "uploads/"+file
    val mimeType: String = MimeTypes.forFileName( fileResUri ).fold(ContentTypes.BINARY)(addCharsetIfNeeded)
    val serveFile = new java.io.File(fileResUri)
    if( serveFile.exists() ){
      val fileContent: Enumerator[Array[Byte]] = Enumerator.fromFile( serveFile )
      //Ok.sendFile(serveFile).as( mimeType )
      val response = Result(
        ResponseHeader(
          OK,
          Map(
            CONTENT_LENGTH -> serveFile.length.toString,
            CONTENT_TYPE -> mimeType
          )
        ),
        fileContent
      )
      response
    }
    else {
      NotFound
    }
  }

  def addCharsetIfNeeded(mimeType: String): String =
    if (MimeTypes.isText(mimeType)) s"$mimeType; charset=$defaultCharSet" else mimeType

  lazy val defaultCharSet = config(_.getString("default.charset")).getOrElse("utf-8")

  def config[T](lookup: Configuration => Option[T]): Option[T] = for {
    app <- Play.maybeApplication
    value <- lookup(app.configuration)
  } yield value

}

But this method will cause some troubles in case of packaged-build deployments.

Which means, using the Play's Asset thing would be wiser choice. So looking again, the controllers.Assets.at which is actually controllers.Assets.assetAt uses this method at one place,

def resource(name: String): Option[URL] = for {
  app <- Play.maybeApplication
  resource <- app.resource(name)
} yield resource

Which means, it tries to locate the resource in the directories which are part of application's classpath and our uploads folder sure is not one of them. So... we can make play's Assets.at thingy work by adding uploads to classpath.

But... thinking again... If I recall all folders in the classpath are supposed to be packaged in the package to be deployed in-case of packaged-build deployments. And uploaded things will be created by the users, which means they should not be a part of package. Which again means... we should not be trying to access our uploaded things using Play's Assets.at thingy.

So... I think we are better off using our own simpler rudimentary implementation of serveUploadedFiles.

Now add a route in route file as,

GET         /uploads/*file                        controllers.FileServer.serveUploadedFiles( file:String )

Also... Keep in mind that you should not be thinking of using play to serve your uploaded assets. Please use nginx or something similar.

sarveshseri
  • 13,738
  • 28
  • 47
  • i got the following browser error when access the images from URL `The image http://localhost:9999/uploads/test.jpg cannot be displayed because it contains error` – Harmeet Singh Taara Mar 17 '15 at 11:37
  • @Sarvesh I had the exact same idea and tried the same thing, although it returns 404 when trying to load the resource. I tried copying the `uploads` directory at inside `target/web` and at root but it still didn't work. – Kalpak Gadre Mar 17 '15 at 11:48
  • Great efforts @SarveshKumarSingh, but with second code, my file is downloaded, not open in browser. – Harmeet Singh Taara Mar 17 '15 at 13:13
  • 1
    Yes... because we are still missing the `mime`-types for our files. Adding proper `mime` information will enable browser to understand the incoming file type and browser will try to figure out a way to handle it. – sarveshseri Mar 17 '15 at 13:25
  • @HarmeetSinghTaara I have made the changes required to enable rendering of the files by browser ( given browser knows how to handle them, so png, jpeg, pdf's etc should render... others will be downloaded ) – sarveshseri Mar 17 '15 at 14:14
  • @SarveshKumarSingh the, problem i thing we face is that, when we register image within routes like `@routes.Assets.at("assets/images/test.jpg")`. We easily get the image from URL, but without routes, we got an error. – Harmeet Singh Taara Mar 17 '15 at 14:57
  • Well... I did not understand yur last comment. But I think your `uploads` should be accessible now using both of following methods - `@controllers.routes.FileServer.serveUploadedFiles("filename")` in template and `localhost:9999/uploads/filename` in browser address bar. – sarveshseri Mar 17 '15 at 15:04
  • @SarveshKumarSingh come to the chat room, we discuss , what i do. http://chat.stackoverflow.com/rooms/73074/scala-and-play-framework-2-x-x – Harmeet Singh Taara Mar 17 '15 at 15:19