mirror of https://github.com/sbt/sbt.git
Add simple-http-server project
This commit is contained in:
parent
44114ff0ed
commit
641af2af91
14
build.sbt
14
build.sbt
|
|
@ -488,8 +488,20 @@ lazy val plugin = project
|
|||
scriptedBufferLog := false
|
||||
)
|
||||
|
||||
lazy val `simple-web-server` = project
|
||||
.settings(commonSettings)
|
||||
.settings(packAutoSettings)
|
||||
.settings(
|
||||
libraryDependencies ++= Seq(
|
||||
"org.http4s" %% "http4s-blaze-server" % "0.13.2",
|
||||
"org.http4s" %% "http4s-dsl" % "0.13.2",
|
||||
"org.slf4j" % "slf4j-nop" % "1.7.19",
|
||||
"com.github.alexarchambault" %% "case-app" % "1.0.0-RC2"
|
||||
)
|
||||
)
|
||||
|
||||
lazy val `coursier` = project.in(file("."))
|
||||
.aggregate(coreJvm, coreJs, `fetch-js`, testsJvm, testsJs, cache, bootstrap, cli, plugin, web, doc)
|
||||
.aggregate(coreJvm, coreJs, `fetch-js`, testsJvm, testsJs, cache, bootstrap, cli, plugin, web, doc, `simple-web-server`)
|
||||
.settings(commonSettings)
|
||||
.settings(noPublishSettings)
|
||||
.settings(releaseSettings)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
package coursier
|
||||
|
||||
import java.io.{ File, FileOutputStream }
|
||||
import java.nio.channels.{ FileLock, OverlappingFileLockException }
|
||||
|
||||
import org.http4s.dsl._
|
||||
import org.http4s.headers.Authorization
|
||||
import org.http4s.server.blaze.BlazeBuilder
|
||||
import org.http4s.{ BasicCredentials, Challenge, HttpService, Request, Response }
|
||||
|
||||
import caseapp._
|
||||
|
||||
import scalaz.concurrent.Task
|
||||
|
||||
case class SimpleHttpServerApp(
|
||||
@ExtraName("d")
|
||||
@ValueDescription("served directory")
|
||||
directory: String,
|
||||
@ExtraName("h")
|
||||
@ValueDescription("host")
|
||||
host: String = "0.0.0.0",
|
||||
@ExtraName("p")
|
||||
@ValueDescription("port")
|
||||
port: Int = 8080,
|
||||
@ExtraName("P")
|
||||
acceptPost: Boolean,
|
||||
@ExtraName("t")
|
||||
acceptPut: Boolean,
|
||||
@ExtraName("w")
|
||||
@HelpMessage("Accept write requests. Equivalent to -P -t")
|
||||
acceptWrite: Boolean,
|
||||
@ExtraName("v")
|
||||
verbose: Int @@ Counter,
|
||||
@ExtraName("u")
|
||||
@ValueDescription("user")
|
||||
user: String,
|
||||
@ExtraName("P")
|
||||
@ValueDescription("password")
|
||||
password: String,
|
||||
@ExtraName("r")
|
||||
@ValueDescription("realm")
|
||||
realm: String
|
||||
) extends App {
|
||||
|
||||
val baseDir = new File(if (directory.isEmpty) "." else directory)
|
||||
|
||||
val verbosityLevel = Tag.unwrap(verbose)
|
||||
|
||||
def write(path: Seq[String], req: Request): Boolean = {
|
||||
|
||||
val f = new File(baseDir, path.toList.mkString("/"))
|
||||
f.getParentFile.mkdirs()
|
||||
|
||||
var os: FileOutputStream = null
|
||||
var lock: FileLock = null
|
||||
try {
|
||||
os = new FileOutputStream(f)
|
||||
lock =
|
||||
try os.getChannel.tryLock()
|
||||
catch {
|
||||
case _: OverlappingFileLockException =>
|
||||
null
|
||||
}
|
||||
|
||||
if (lock == null)
|
||||
false
|
||||
else {
|
||||
req.body.runLog.run.foreach { b =>
|
||||
b.copyToStream(os)
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
} finally {
|
||||
if (lock != null)
|
||||
lock.release()
|
||||
if (os != null)
|
||||
os.close()
|
||||
}
|
||||
}
|
||||
|
||||
if (user.nonEmpty && password.isEmpty)
|
||||
Console.err.println(
|
||||
"Warning: authentication enabled but no password specified. " +
|
||||
"Specify one with the --password or -P option."
|
||||
)
|
||||
|
||||
if (password.nonEmpty && user.isEmpty)
|
||||
Console.err.println(
|
||||
"Warning: authentication enabled but no user specified. " +
|
||||
"Specify one with the --user or -u option."
|
||||
)
|
||||
|
||||
if ((user.nonEmpty || password.nonEmpty) && realm.isEmpty)
|
||||
Console.err.println(
|
||||
"Warning: authentication enabled but no realm specified. " +
|
||||
"Specify one with the --realm or -r option."
|
||||
)
|
||||
|
||||
val unauthorized = Unauthorized(Challenge("Basic", realm))
|
||||
|
||||
def authenticated(pf: PartialFunction[Request, Task[Response]]): HttpService =
|
||||
authenticated0(HttpService(pf))
|
||||
|
||||
def authenticated0(service: HttpService): HttpService =
|
||||
if (user.isEmpty && password.isEmpty)
|
||||
service
|
||||
else
|
||||
HttpService {
|
||||
case req =>
|
||||
req.headers.get(Authorization) match {
|
||||
case None =>
|
||||
unauthorized
|
||||
case Some(auth) =>
|
||||
auth.credentials match {
|
||||
case basic: BasicCredentials =>
|
||||
if (basic.username == user && basic.password == password)
|
||||
service.run(req)
|
||||
else
|
||||
unauthorized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def putService = authenticated {
|
||||
case req @ PUT -> path =>
|
||||
if (verbosityLevel >= 1)
|
||||
Console.err.println(s"PUT $path")
|
||||
|
||||
if (write(path.toList, req))
|
||||
Ok()
|
||||
else
|
||||
Locked()
|
||||
}
|
||||
|
||||
def postService = authenticated {
|
||||
case req @ POST -> path =>
|
||||
if (verbosityLevel >= 1)
|
||||
Console.err.println(s"POST $path")
|
||||
|
||||
if (write(path.toList, req))
|
||||
Ok()
|
||||
else
|
||||
Locked()
|
||||
}
|
||||
|
||||
def getService = authenticated {
|
||||
case GET -> path =>
|
||||
if (verbosityLevel >= 1)
|
||||
Console.err.println(s"GET $path")
|
||||
|
||||
val f = new File(baseDir, path.toList.mkString("/"))
|
||||
if (f.exists())
|
||||
Ok(f)
|
||||
else
|
||||
NotFound()
|
||||
}
|
||||
|
||||
val builder = {
|
||||
var b = BlazeBuilder.bindHttp(port, host)
|
||||
|
||||
if (acceptWrite || acceptPut)
|
||||
b = b.mountService(putService)
|
||||
if (acceptWrite || acceptPost)
|
||||
b = b.mountService(postService)
|
||||
|
||||
b = b.mountService(getService)
|
||||
|
||||
b
|
||||
}
|
||||
|
||||
builder
|
||||
.run
|
||||
.awaitShutdown()
|
||||
|
||||
}
|
||||
|
||||
object SimpleHttpServer extends AppOf[SimpleHttpServerApp]
|
||||
Loading…
Reference in New Issue