Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add static fileserver #2450

Merged
merged 5 commits into from
Sep 30, 2023
Merged

add static fileserver #2450

merged 5 commits into from
Sep 30, 2023

Conversation

TomTriple
Copy link
Contributor

@TomTriple TomTriple commented Sep 18, 2023

I have a simple static fileserver as middleware, which provides an API like that:

package example

import zio._
import zio.http._

object AppMain extends ZIOAppDefault {

  val staticServeMiddleware = StaticServe.middleware(
      Path.root / "images", 
      StaticServe
        .fromDirectory("/opt/repos/os/aaa/images")
        .orElse(StaticServe.fromDirectory("/opt/repos/os/aaa/assets"))
        .orElse(StaticServe.fromResource)
        .orElse(StaticServe.fromFileZIO(ZIO.succeed(new java.io.File("./fallback.png"))))    
  )

  val appDashboard = new AppDashboard()
  val appLogin = new AppLogin(appDashboard)
  val appRoutes = (
    appLogin.routes ++ appDashboard.routes
  )

  override val run =
    Server.serve(appRoutes.toHttpApp @@ staticServeMiddleware).provide(Server.default)

} 

I also added checks to anticipate (and prevent) traversal attacks (same as in akka http):

  • prevent fishy requests with ".." in path segments
  • prevent serving files outside of the configured document root directory

@codecov-commenter
Copy link

codecov-commenter commented Sep 18, 2023

Codecov Report

Attention: 18 lines in your changes are missing coverage. Please review.

Comparison is base (5906d30) 64.75% compared to head (cbee2be) 64.58%.

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2450      +/-   ##
==========================================
- Coverage   64.75%   64.58%   -0.17%     
==========================================
  Files         135      135              
  Lines        7135     7153      +18     
  Branches     1200     1207       +7     
==========================================
  Hits         4620     4620              
- Misses       2515     2533      +18     
Files Coverage Δ
zio-http/src/main/scala/zio/http/Middleware.scala 57.64% <0.00%> (-15.49%) ⬇️

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

}
}

def middleware[R, E](path: Path, staticServe: StaticServe[R, E, Any, Response]): Middleware[R] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be toMiddleware.

Also, I think there is value in only having a public Middleware.staticServer interface, and hiding all these types. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think it's a good idea to stay consistent with Middleware in general.

If I see that correctly we don´t need anything composable for the user when configuring a static server. It could be enough to only support the two main use-cases:

  1. publish a given directory at a specified path: Middleware.staticServer.fromDirectory(atPath:Path, docRoot: File):Middleware

  2. publish resources at a specified path: Middleware.staticServer.fromResources(atPath:Path):Middleware

Is that what you mean? If yes, I think we should do it that way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One drawback would be that we could not add caching of static assets transparently, no (assuming we want to do that)? With StaticServe we could add a new operator cached:StaticServe that can cache results. Maybe it's best to go with 1. and 2. from above and wait for feedback...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would go for:

object Middleware {
  ...
  def serveDirectory(path: Path, docRoot: File, cached: Boolean = false): Middleware
}

Then it has minimal impact on the surface area, and all of these helper classes / functions can be made private inside the companion object of Middleware.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I discarded adding an option for cache as that should be more carefully thought through - if I find time to do that we can still add that option without any problems. Besides that I changed the API according to your suggestion!

@jdegoes
Copy link
Member

jdegoes commented Sep 30, 2023

One more suggestion as a followup PR: add this as either an HttpApp or as a Route (e.g. Route.serveDirectory(...)). Since not everyone might look for it inside middleware.

@jdegoes jdegoes merged commit de31152 into zio:main Sep 30, 2023
13 checks passed
sullivan- added a commit to sullivan-/zio-http that referenced this pull request Jun 6, 2024
As it is, `Middleware.serveResources(path)` going to serve any file at all under src/main/resources, including e.g. somebody's reference.conf file.
I added a resourcePrefix parameter for this, so that the resources served can be limited to, e.g.,  src/main/resources/public.

I also added delegates for serveResources and serveDirectory in Routes, as per this suggestion:

zio#2450 (comment)
jdegoes pushed a commit that referenced this pull request Jun 6, 2024
As it is, `Middleware.serveResources(path)` going to serve any file at all under src/main/resources, including e.g. somebody's reference.conf file.
I added a resourcePrefix parameter for this, so that the resources served can be limited to, e.g.,  src/main/resources/public.

I also added delegates for serveResources and serveDirectory in Routes, as per this suggestion:

#2450 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants