Image upload and serving example

276 views
Skip to first unread message

David Pollak

unread,
Dec 1, 2009, 12:26:26 AM12/1/09
to liftweb
Folks,

Lately there's been a bunch of chatter on the list about image upload and figuring out how to put the image files in the right place to serve them again.

I've written a simple example of image uploading, storing the image in the RDBMS and then serving the image.  You can find the code at:

The file upload snippet is very simple:

class DoUpload {
  private def saveFile(fp: FileParamHolder): Unit = {
    fp.file match {
      case null =>
      case x if x.length == 0 =>
      case x =>
        val blob = ImageBlob.create.image(x).saveMe
        ImageInfo.create.name(fp.fileName).mimeType(fp.mimeType).blob(blob).saveMe
        S.notice("Thanks for the upload")
        S.redirectTo("/")
    }
  }

  def render(in: NodeSeq): NodeSeq =
  bind("upload", in, "file" -> SHtml.fileUpload(saveFile _))

}

If the blob is uploaded, it's put in a row in ImageBlob and an ImageInfo row is created to store the name and mime type.  There are two different rows so that one doesn't have to pull the whole blob back when you're checking for the upload date.

Serving the image is similarly concise:

  def serveImage: LiftRules.DispatchPF = {
    case req @ Req("images" :: _ :: Nil, _, GetRequest) if findFromRequest(req).isDefined =>
      () => {
        val info = findFromRequest(req).open_! // open is valid here because we just tested in the guard

        // Test for expiration
        req.testFor304(info.date, "Expires" -> toInternetDate(millis + 30.days)) or
        // load the blob and return it
        info.blob.obj.map(blob => InMemoryResponse(blob.image, List(("Last-Modified", toInternetDate(info.date.is)),
                                                                    ("Expires", toInternetDate(millis + 30.days)),
                                                                    ("Content-Type", info.mimeType.is)), Nil,  200))
      }
  }


If the request matches /images/xxx where xxx is a name in the ImageInfo table, check to see if we can return a 304 (not modified).  If not, we pull the blob from the database and return it.

I hope this helps folks who are looking to manage and serve images.

Thanks,

David

--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Surf the harmonics

Marius

unread,
Dec 1, 2009, 2:58:40 AM12/1/09
to Lift
This sounds like a good wiki subject. Wiki gardeners ?

Br's,
Marius
> Beginning Scalahttp://www.apress.com/book/view/1430219890

DMB

unread,
Dec 1, 2009, 5:49:32 AM12/1/09
to Lift
Just what I was looking for, thanks!

By the way, is there a way to assign a "name" attribute to a file
upload input field? The reason is I have a legacy piece of code that
does HTTP uploads by simulating a form submit. This code requires that
the form field name be known in advance, for obvious reasons.

On Nov 30, 9:26 pm, David Pollak <feeder.of.the.be...@gmail.com>
wrote:
> Beginning Scalahttp://www.apress.com/book/view/1430219890

David Pollak

unread,
Dec 1, 2009, 1:14:51 PM12/1/09
to lif...@googlegroups.com
If you have the Req (request), you can do:

val req: Req = ...
val theFile: Option[FileParamHolder] = req.uploadedFiles.find(_.name == "my_param_name")



--

You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.





--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890

DMB

unread,
Dec 1, 2009, 11:03:00 PM12/1/09
to Lift
Ended up doing this:

def show(xhtml: NodeSeq) : NodeSeq = {
if(S.post_?) {
val f : Option[FileParamHolder] = S.request match {
case Empty => Empty
case Full(req) =>
req.uploadedFiles.find(_.name == "file_upload")
}

if(f.isDefined) {
acceptFile(f.get)
}
}
return xhtml
}

And putting a bare upload field in the snippet, like you sugested. I
still wonder if the binding method is the best place for this, though.

On Dec 1, 10:14 am, David Pollak <feeder.of.the.be...@gmail.com>
wrote:
> > liftweb+u...@googlegroups.com<liftweb%2Bunsu...@googlegroups.com>
> > .
> > For more options, visit this group at
> >http://groups.google.com/group/liftweb?hl=en.
>
> --

David Pollak

unread,
Dec 1, 2009, 11:07:34 PM12/1/09
to lif...@googlegroups.com
On Tue, Dec 1, 2009 at 8:03 PM, DMB <combu...@gmail.com> wrote:
Ended up doing this:

   def show(xhtml: NodeSeq) : NodeSeq = {
       if(S.post_?) {
           val f : Option[FileParamHolder] = S.request match {
               case Empty => Empty
               case Full(req) =>
                   req.uploadedFiles.find(_.name == "file_upload")
           }

           if(f.isDefined) {
               acceptFile(f.get)
           }
       }
       return xhtml
   }

This is a place for a for comprehension:


def show(xhtml: NodeSeq): NodeSeq = {
  for {
    request <- S.request if S.post_?
    file <- request.uploadedFiles.find(_.name == "file_upload")
  } acceptFile(f.get)
 
  xhtml
}

Note that the code is shorter, reads better and has no branches in it.
 
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.

For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.





--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890

DMB

unread,
Dec 2, 2009, 12:15:50 AM12/2/09
to Lift
You don't even need f.get in there as it turns out. "f.get" should
just be "file". Beautiful. Thanks for the tip.

On Dec 1, 8:07 pm, David Pollak <feeder.of.the.be...@gmail.com> wrote:
> > <liftweb%2Bunsu...@googlegroups.com<liftweb%252Buns...@googlegroups.com>

jon

unread,
Dec 2, 2009, 1:24:20 PM12/2/09
to Lift
For those that are using s3, I created a MappedS3Image MappedField for
automating the storage of image uploads to s3 with a unique identifier
stored in the RDBMS. If anyone's interested I could clean and share
the code.

usage like this: user.avatar.setFromUpload(fileParamHolderBox) or
user.avatar.setFromUrl(urlString)

- Jon
> > > <liftweb%2Bunsu...@googlegroups.com<liftweb%252Bunsubscribe@googlegroup s.com>

Peter Robinett

unread,
Dec 2, 2009, 2:07:03 PM12/2/09
to Lift
Jon, that sounds very interesting and I'd love to see it. S3 support
in general would be a great module for Lift, in my opinion.

Peter

Hannes

unread,
Nov 29, 2010, 5:37:41 AM11/29/10
to lif...@googlegroups.com
Hi David,

Does that code still exist? Because the link is dead and I couldn't find it under your profile (http://github.com/dpp).

thanks.

Folks,

Lately there's been a bunch of chatter on the list about image upload and figuring out how to put the image files in the right place to serve them again.

I've written a simple example of image uploading, storing the image in the RDBMS and then serving the image. ï¿œYou can find the code at:

The file upload snippet is very simple:

class DoUpload {
ᅵᅵprivate def saveFile(fp: FileParamHolder): Unit = {
ᅵᅵ ᅵfp.file match {
ᅵᅵ ᅵ ᅵcase null =>
ᅵᅵ ᅵ ᅵcase x if x.length == 0 =>
ᅵᅵ ᅵ ᅵcase x =>
ᅵᅵ ᅵ ᅵ ᅵval blob = ImageBlob.create.image(x).saveMe
ᅵᅵ ᅵ ᅵ ᅵImageInfo.create.name(fp.fileName).mimeType(fp.mimeType).blob(blob).saveMe
ᅵᅵ ᅵ ᅵ ᅵS.notice("Thanks for the upload")
ᅵᅵ ᅵ ᅵ ᅵS.redirectTo("/")
ᅵᅵ ᅵ}
ᅵᅵ}

ᅵᅵdef render(in: NodeSeq): NodeSeq =
ᅵᅵbind("upload", in, "file" -> SHtml.fileUpload(saveFile _))

}

If the blob is uploaded, it's put in a row in ImageBlob and an ImageInfo row is created to store the name and mime type. ï¿œThere are two different rows so that one doesn't have to pull the whole blob back when you're checking for the upload date.

Serving the image is similarly concise:

ᅵᅵdef serveImage: LiftRules.DispatchPF = {
ᅵᅵ ᅵcase req @ Req("images" :: _ :: Nil, _, GetRequest) if findFromRequest(req).isDefined =>
ᅵᅵ ᅵ ᅵ() => {
ᅵᅵ ᅵ ᅵ ᅵval info = findFromRequest(req).open_! // open is valid here because we just tested in the guard

ᅵᅵ ᅵ ᅵ ᅵ// Test for expiration
ᅵᅵ ᅵ ᅵ ᅵreq.testFor304(info.date, "Expires" -> toInternetDate(millis + 30.days)) or
ᅵᅵ ᅵ ᅵ ᅵ// load the blob and return it
ᅵᅵ ᅵ ᅵ ᅵinfo.blob.obj.map(blob => InMemoryResponse(blob.image, List(("Last-Modified", toInternetDate(info.date.is)),
ᅵᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ("Expires", toInternetDate(millis + 30.days)),
ᅵᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ ᅵ("Content-Type", info.mimeType.is)), Nil, ᅵ200))
ᅵᅵ ᅵ ᅵ}
ᅵᅵ}


If the request matches /images/xxx where xxx is a name in the ImageInfo table, check to see if we can return a 304 (not modified). ï¿œIf not, we pull the blob from the database and return it.

I hope this helps folks who are looking to manage and serve images.

Thanks,

David

--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Surf the harmonics

--

You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.

David Pollak

unread,
Nov 29, 2010, 1:02:33 PM11/29/10
to lif...@googlegroups.com
On Mon, Nov 29, 2010 at 2:37 AM, Hannes <hannes...@gmx.li> wrote:
Hi David,

Does that code still exist? Because the link is dead and I couldn't find it under your profile (http://github.com/dpp).

Then it's not in GitHub anymore.
 

thanks.

Folks,

Lately there's been a bunch of chatter on the list about image upload and figuring out how to put the image files in the right place to serve them again.

I've written a simple example of image uploading, storing the image in the RDBMS and then serving the image.  You can find the code at:

The file upload snippet is very simple:

class DoUpload {
  private def saveFile(fp: FileParamHolder): Unit = {
    fp.file match {
      case null =>
      case x if x.length == 0 =>
      case x =>
        val blob = ImageBlob.create.image(x).saveMe
        ImageInfo.create.name(fp.fileName).mimeType(fp.mimeType).blob(blob).saveMe
        S.notice("Thanks for the upload")
        S.redirectTo("/")
    }
  }

  def render(in: NodeSeq): NodeSeq =
  bind("upload", in, "file" -> SHtml.fileUpload(saveFile _))

}

If the blob is uploaded, it's put in a row in ImageBlob and an ImageInfo row is created to store the name and mime type.  There are two different rows so that one doesn't have to pull the whole blob back when you're checking for the upload date.

Serving the image is similarly concise:

  def serveImage: LiftRules.DispatchPF = {
    case req @ Req("images" :: _ :: Nil, _, GetRequest) if findFromRequest(req).isDefined =>
      () => {
        val info = findFromRequest(req).open_! // open is valid here because we just tested in the guard

        // Test for expiration
        req.testFor304(info.date, "Expires" -> toInternetDate(millis + 30.days)) or
        // load the blob and return it
        info.blob.obj.map(blob => InMemoryResponse(blob.image, List(("Last-Modified", toInternetDate(info.date.is)),
                                                                    ("Expires", toInternetDate(millis + 30.days)),
                                                                    ("Content-Type", info.mimeType.is)), Nil,  200))
      }
  }


If the request matches /images/xxx where xxx is a name in the ImageInfo table, check to see if we can return a 304 (not modified).  If not, we pull the blob from the database and return it.

I hope this helps folks who are looking to manage and serve images.

Thanks,

David

--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Surf the harmonics

--

You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.

For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.

--
You received this message because you are subscribed to the Google Groups "Lift" group.
To post to this group, send email to lif...@googlegroups.com.
To unsubscribe from this group, send email to liftweb+u...@googlegroups.com.

For more options, visit this group at http://groups.google.com/group/liftweb?hl=en.



--
Lift, the simply functional web framework http://liftweb.net
Beginning Scala http://www.apress.com/book/view/1430219890
Follow me: http://twitter.com/dpp
Blog: http://goodstuff.im
Surf the harmonics

Jim Wise

unread,
Nov 29, 2010, 1:08:57 PM11/29/10
to lif...@googlegroups.com
David Pollak <feeder.of...@gmail.com> writes:

> On Mon, Nov 29, 2010 at 2:37 AM, Hannes <hannes...@gmx.li> wrote:
>
> Hi David,
>
> Does that code still exist? Because the link is dead and I couldn't find it under your profile (http://github.com/dpp).
>
> Then it's not in GitHub anymore.

For what it's worth, I have an image gallery based on the original image
upload example at

https://github.com/jimwise/shared/tree/master/gallery/

This is updated to work with 2.1, and includes a browseable gallery,
image rename and categorization, and image/category deletion.

--
Jim Wise
jw...@draga.com

Hannes

unread,
Nov 29, 2010, 2:06:32 PM11/29/10
to lif...@googlegroups.com
Cool, thanks!

I'll have a look at it...

Hannes

unread,
Dec 2, 2010, 3:52:40 AM12/2/10
to lif...@googlegroups.com
Perfect you saved my day!

thanks.

Hannes

unread,
Dec 2, 2010, 7:34:54 AM12/2/10
to lif...@googlegroups.com
Hi Jim,

One question:

The following object/field is part of a trait which I mix into a my Mapper class:

   object imagePath extends MappedString(this.asInstanceOf[MapperType], 200) {

    def notEmpty(input: String) = {
      println("input=" + input)
      if (input.isEmpty) {
        List(FieldError(this, "No file selected"))
      }
      else List[FieldError]()
    }

    override def _toForm = {
      Full(SHtml.fileUpload(saveFile _))
    }

    override def displayName = "PICTURE"
    override def validations = notEmpty _ :: Nil

   } // object imagePath


  private def saveFile(fp: FileParamHolder): Unit = {
    println("saveFile=" + fp.fileName)
  }


The create form (generated with CRUDify) is rendered nicely, but when I select some file, it seems like saveFile is not called, and when I click "create", my validation tells me "No file selected".?

What am I missing?


thanks.
Reply all
Reply to author
Forward
0 new messages