diff --git a/build.sbt b/build.sbt
index 8aece00de..ef39bd37a 100644
--- a/build.sbt
+++ b/build.sbt
@@ -191,19 +191,13 @@ lazy val `http-server` = project
.settings(
shared,
generatePack,
- dontPublishIn("2.10", "2.12"),
- name := "http-server-java7",
- libs ++= {
- if (scalaBinaryVersion.value == "2.11")
- Seq(
- Deps.http4sBlazeServer,
- Deps.http4sDsl,
- Deps.slf4jNop,
- Deps.caseApp
- )
- else
- Seq()
- }
+ name := "http-server",
+ libs ++= Seq(
+ Deps.http4sBlazeServer,
+ Deps.http4sDsl,
+ Deps.slf4jNop,
+ Deps.caseApp12
+ )
)
lazy val okhttp = project
diff --git a/http-server/src/main/scala-2.11/coursier/HttpServer.scala b/http-server/src/main/scala-2.11/coursier/HttpServer.scala
deleted file mode 100644
index 283cbedb4..000000000
--- a/http-server/src/main/scala-2.11/coursier/HttpServer.scala
+++ /dev/null
@@ -1,274 +0,0 @@
-package coursier
-
-import java.io.{ File, FileOutputStream }
-import java.net.NetworkInterface
-import java.nio.channels.{ FileLock, OverlappingFileLockException }
-
-import org.http4s.dsl._
-import org.http4s.headers.{ Authorization, `Content-Type` }
-import org.http4s.server.HttpService
-import org.http4s.server.blaze.BlazeBuilder
-import org.http4s.{ BasicCredentials, Challenge, EmptyBody, MediaType, Request, Response }
-
-import caseapp._
-
-import scala.collection.JavaConverters._
-
-import scalaz.concurrent.Task
-
-final case class HttpServerApp(
- @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("s")
- acceptPost: Boolean,
- @ExtraName("t")
- acceptPut: Boolean,
- @ExtraName("w")
- @HelpMessage("Accept write requests. Equivalent to -s -t")
- acceptWrite: Boolean,
- @ExtraName("v")
- verbose: Int @@ Counter,
- @ExtraName("q")
- quiet: Boolean,
- @ExtraName("u")
- @ValueDescription("user")
- user: String,
- @ExtraName("P")
- @ValueDescription("password")
- password: String,
- @ExtraName("r")
- @ValueDescription("realm")
- realm: String,
- @ExtraName("l")
- @HelpMessage("Generate content listing pages for directories")
- listPages: Boolean
-) extends App {
-
- val baseDir = new File(if (directory.isEmpty) "." else directory)
-
- val verbosityLevel = Tag.unwrap(verbose) - (if (quiet) 1 else 0)
-
- 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 =>
- def warn(msg: => String) =
- if (verbosityLevel >= 1)
- Console.err.println(s"${req.method.name} ${req.uri.path}: $msg")
-
- req.headers.get(Authorization) match {
- case None =>
- warn("no authentication provided")
- unauthorized
- case Some(auth) =>
- auth.credentials match {
- case basic: BasicCredentials =>
- if (basic.username == user && basic.password == password)
- service.run(req).flatMap {
- case Some(v) => Task.now(v)
- case None => NotFound()
- }
- else {
- warn {
- val msg =
- if (basic.username == user)
- "wrong password"
- else
- s"unrecognized user ${basic.username}"
-
- s"authentication failed ($msg)"
- }
- unauthorized
- }
- case _ =>
- warn("no basic credentials found")
- 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 isDirectory(f: File): Task[Option[Boolean]] =
- Task {
- if (f.isDirectory)
- Some(true)
- else if (f.isFile)
- Some(false)
- else
- None
- }
-
- def directoryListingPage(dir: File, title: String): Task[String] =
- Task {
- val entries = dir
- .listFiles()
- .flatMap { f =>
- def name = f.getName
- if (f.isDirectory)
- Seq(name + "/")
- else if (f.isFile)
- Seq(name)
- else
- Nil
- }
-
- // meh escaping
- // TODO Use to scalatags to generate that
- s"""
- |
- |
- |$title
- |
- |
- |
- |${entries.map(e => " - " + e + "
").mkString("\n")}
- |
- |
- |
- """.stripMargin
- }
-
- def getService = authenticated {
- case (method @ (GET | HEAD)) -> path =>
- if (verbosityLevel >= 1)
- Console.err.println(s"${method.name} $path")
-
- val relPath = path.toList.mkString("/")
- val f = new File(baseDir, relPath)
- val resp =
- for {
- isDirOpt <- isDirectory(f)
- resp <- isDirOpt match {
- case Some(true) if listPages =>
- directoryListingPage(f, relPath).flatMap(page =>
- Ok(page).withContentType(Some(`Content-Type`(MediaType.`text/html`)))
- )
- case Some(false) => Ok(f)
- case _ => NotFound()
- }
- } yield resp
-
- method match {
- case HEAD =>
- resp.map(_.copy(body = EmptyBody))
- case _ =>
- resp
- }
- }
-
- 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
- }
-
- if (verbosityLevel >= 0) {
- Console.err.println(s"Listening on http://$host:$port")
-
- if (verbosityLevel >= 1 && host == "0.0.0.0") {
- Console.err.println(s"Listening on addresses")
- for (itf <- NetworkInterface.getNetworkInterfaces.asScala; addr <- itf.getInetAddresses.asScala)
- Console.err.println(s" ${addr.getHostAddress} (${itf.getName})")
- }
- }
-
- builder
- .run
- .awaitShutdown()
-
-}
-
-object HttpServer extends AppOf[HttpServerApp]
diff --git a/http-server/src/main/scala/coursier/HttpServer.scala b/http-server/src/main/scala/coursier/HttpServer.scala
new file mode 100644
index 000000000..fbef7f8a9
--- /dev/null
+++ b/http-server/src/main/scala/coursier/HttpServer.scala
@@ -0,0 +1,271 @@
+package coursier
+
+import java.io.{File, FileOutputStream}
+import java.net.NetworkInterface
+import java.nio.channels.{FileLock, OverlappingFileLockException}
+
+import org.http4s._
+import org.http4s.dsl._
+import org.http4s.headers.{Authorization, `Content-Type`}
+import org.http4s.server.blaze.BlazeBuilder
+
+import caseapp._
+
+import scala.collection.JavaConverters._
+import scalaz.concurrent.Task
+
+final case class HttpServerOptions(
+ @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("s")
+ acceptPost: Boolean,
+ @ExtraName("t")
+ acceptPut: Boolean,
+ @ExtraName("w")
+ @HelpMessage("Accept write requests. Equivalent to -s -t")
+ acceptWrite: Boolean,
+ @ExtraName("v")
+ verbose: Int @@ Counter,
+ @ExtraName("q")
+ quiet: Boolean,
+ @ExtraName("u")
+ @ValueDescription("user")
+ user: String,
+ @ExtraName("P")
+ @ValueDescription("password")
+ password: String,
+ @ExtraName("r")
+ @ValueDescription("realm")
+ realm: String,
+ @ExtraName("l")
+ @HelpMessage("Generate content listing pages for directories")
+ listPages: Boolean
+)
+
+object HttpServer extends CaseApp[HttpServerOptions] {
+ def run(options: HttpServerOptions, args: RemainingArgs): Unit = {
+
+ val baseDir = new File(if (options.directory.isEmpty) "." else options.directory)
+
+ val verbosityLevel = Tag.unwrap(options.verbose) - (if (options.quiet) 1 else 0)
+
+ 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.unsafePerformSync.foreach { b =>
+ b.copyToStream(os)
+ }
+
+ true
+ }
+ } finally {
+ if (lock != null)
+ lock.release()
+ if (os != null)
+ os.close()
+ }
+ }
+
+ if (options.user.nonEmpty && options.password.isEmpty)
+ Console.err.println(
+ "Warning: authentication enabled but no password specified. " +
+ "Specify one with the --password or -P option."
+ )
+
+ if (options.password.nonEmpty && options.user.isEmpty)
+ Console.err.println(
+ "Warning: authentication enabled but no user specified. " +
+ "Specify one with the --user or -u option."
+ )
+
+ if ((options.user.nonEmpty || options.password.nonEmpty) && options.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", options.realm))
+
+ def authenticated(pf: PartialFunction[Request, Task[Response]]): HttpService =
+ authenticated0(HttpService(pf))
+
+ def authenticated0(service: HttpService): HttpService =
+ if (options.user.isEmpty && options.password.isEmpty)
+ service
+ else
+ HttpService {
+ case req =>
+ def warn(msg: => String) =
+ if (verbosityLevel >= 1)
+ Console.err.println(s"${req.method.name} ${req.uri.path}: $msg")
+
+ req.headers.get(Authorization) match {
+ case None =>
+ warn("no authentication provided")
+ unauthorized
+ case Some(auth) =>
+ auth.credentials match {
+ case basic: BasicCredentials =>
+ if (basic.username == options.user && basic.password == options.password)
+ service.run(req)
+ else {
+ warn {
+ val msg =
+ if (basic.username == options.user)
+ "wrong password"
+ else
+ s"unrecognized user ${basic.username}"
+
+ s"authentication failed ($msg)"
+ }
+ unauthorized
+ }
+ case _ =>
+ warn("no basic credentials found")
+ 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 isDirectory(f: File): Task[Option[Boolean]] =
+ Task {
+ if (f.isDirectory)
+ Some(true)
+ else if (f.isFile)
+ Some(false)
+ else
+ None
+ }
+
+ def directoryListingPage(dir: File, title: String): Task[String] =
+ Task {
+ val entries = dir
+ .listFiles()
+ .flatMap { f =>
+ def name = f.getName
+ if (f.isDirectory)
+ Seq(name + "/")
+ else if (f.isFile)
+ Seq(name)
+ else
+ Nil
+ }
+
+ // meh escaping
+ // TODO Use to scalatags to generate that
+ s"""
+ |
+ |
+ |$title
+ |
+ |
+ |
+ |${entries.map(e => " - " + e + "
").mkString("\n")}
+ |
+ |
+ |
+ """.stripMargin
+ }
+
+ def getService = authenticated {
+ case (method @ (GET | HEAD)) -> path =>
+ if (verbosityLevel >= 1)
+ Console.err.println(s"${method.name} $path")
+
+ val relPath = path.toList.mkString("/")
+ val f = new File(baseDir, relPath)
+ val resp =
+ for {
+ isDirOpt <- isDirectory(f)
+ resp <- isDirOpt match {
+ case Some(true) if options.listPages =>
+ directoryListingPage(f, relPath).flatMap(page =>
+ Ok(page).withContentType(Some(`Content-Type`(MediaType.`text/html`)))
+ )
+ case Some(false) => Ok(f)
+ case _ => NotFound()
+ }
+ } yield resp
+
+ method match {
+ case HEAD =>
+ resp.map(_.copy(body = EmptyBody))
+ case _ =>
+ resp
+ }
+ }
+
+ val builder = {
+ var b = BlazeBuilder.bindHttp(options.port, options.host)
+
+ if (options.acceptWrite || options.acceptPut)
+ b = b.mountService(putService)
+ if (options.acceptWrite || options.acceptPost)
+ b = b.mountService(postService)
+
+ b = b.mountService(getService)
+
+ b
+ }
+
+ if (verbosityLevel >= 0) {
+ Console.err.println(s"Listening on http://${options.host}:${options.port}")
+
+ if (verbosityLevel >= 1 && options.host == "0.0.0.0") {
+ Console.err.println(s"Listening on addresses")
+ for (itf <- NetworkInterface.getNetworkInterfaces.asScala; addr <- itf.getInetAddresses.asScala)
+ Console.err.println(s" ${addr.getHostAddress} (${itf.getName})")
+ }
+ }
+
+ builder
+ .run
+ .awaitShutdown()
+ }
+
+}
diff --git a/project/Deps.scala b/project/Deps.scala
index 4f0461186..d85772737 100644
--- a/project/Deps.scala
+++ b/project/Deps.scala
@@ -10,7 +10,8 @@ object Deps {
def scalaXml = "org.scala-lang.modules" %% "scala-xml" % "1.0.6"
def scalazConcurrent = "org.scalaz" %% "scalaz-concurrent" % SharedVersions.scalaz
def caseApp = "com.github.alexarchambault" %% "case-app" % "1.1.3"
- def http4sBlazeServer = "org.http4s" %% "http4s-blazeserver" % SharedVersions.http4s
+ def caseApp12 = "com.github.alexarchambault" %% "case-app" % "1.2.0-M2"
+ def http4sBlazeServer = "org.http4s" %% "http4s-blaze-server" % SharedVersions.http4s
def http4sDsl = "org.http4s" %% "http4s-dsl" % SharedVersions.http4s
def slf4jNop = "org.slf4j" % "slf4j-nop" % "1.7.22"
def okhttpUrlConnection = "com.squareup.okhttp" % "okhttp-urlconnection" % "2.7.5"
diff --git a/project/SharedVersions.scala b/project/SharedVersions.scala
index 3442e7b89..1eecde8b2 100644
--- a/project/SharedVersions.scala
+++ b/project/SharedVersions.scala
@@ -2,8 +2,7 @@
object SharedVersions {
val scalaz = "7.2.8"
-
- // last http4s version compatible with Java 7 (Travis-based Mac CI still on Java 7...)
- val http4s = "0.8.6"
+
+ val http4s = "0.15.9a"
}
\ No newline at end of file
diff --git a/scripts/launch-test-repo.sh b/scripts/launch-test-repo.sh
index 0e0685a7b..fca2ec105 100755
--- a/scripts/launch-test-repo.sh
+++ b/scripts/launch-test-repo.sh
@@ -8,12 +8,11 @@ cd "$(dirname "$0")/.."
# synchronously fill cache so that two runs of this script don't try to download
# a same file at the same time (and one of them fail because of locks)
./coursier fetch \
- "io.get-coursier:http-server-java7_2.11:$VERSION" \
+ "io.get-coursier:http-server_2.11:$VERSION" \
-r https://dl.bintray.com/scalaz/releases
./coursier launch \
- "io.get-coursier:http-server-java7_2.11:$VERSION" \
- -r https://dl.bintray.com/scalaz/releases \
+ "io.get-coursier:http-server_2.11:$VERSION" \
-- \
-d tests/jvm/src/test/resources/test-repo/http/abc.com \
-u user -P pass -r realm \
diff --git a/scripts/start-it-auth-server.ps1 b/scripts/start-it-auth-server.ps1
index d9ffdda0d..d1c8456c9 100644
--- a/scripts/start-it-auth-server.ps1
+++ b/scripts/start-it-auth-server.ps1
@@ -1,3 +1,3 @@
# see https://stackoverflow.com/questions/2224350/powershell-start-job-working-directory/2246542#2246542
Set-Location $args[0]
-& java -jar -noverify coursier launch -r https://dl.bintray.com/scalaz/releases io.get-coursier:http-server-java7_2.11:1.0.0-SNAPSHOT -- -d tests/jvm/src/test/resources/test-repo/http/abc.com -u user -P pass -r realm --port 8080 --list-pages -v
+& java -jar -noverify coursier launch io.get-coursier:http-server_2.11:1.0.0-SNAPSHOT -- -d tests/jvm/src/test/resources/test-repo/http/abc.com -u user -P pass -r realm --port 8080 --list-pages -v
diff --git a/scripts/start-it-no-listing-server.ps1 b/scripts/start-it-no-listing-server.ps1
index 0e217bd7a..8d0275bc1 100644
--- a/scripts/start-it-no-listing-server.ps1
+++ b/scripts/start-it-no-listing-server.ps1
@@ -1,3 +1,3 @@
# see https://stackoverflow.com/questions/2224350/powershell-start-job-working-directory/2246542#2246542
Set-Location $args[0]
-& java -jar -noverify coursier launch -r https://dl.bintray.com/scalaz/releases io.get-coursier:http-server-java7_2.11:1.0.0-SNAPSHOT -- -d tests/jvm/src/test/resources/test-repo/http/abc.com -u user -P pass -r realm --port 8081 -v
+& java -jar -noverify coursier launch io.get-coursier:http-server_2.11:1.0.0-SNAPSHOT -- -d tests/jvm/src/test/resources/test-repo/http/abc.com -u user -P pass -r realm --port 8081 -v