From 131e5ec53ad065461c760c16f68af2522437928d Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Sun, 2 Jun 2013 23:20:00 +0200 Subject: o Tried to improve error handling when the backend server fails. --- src/main/scala/io/trygvis/cj/Explorer.scala | 63 ++++++++++---- src/main/scala/io/trygvis/cj/Views.scala | 130 +++++++++++++++++++++------- 2 files changed, 144 insertions(+), 49 deletions(-) diff --git a/src/main/scala/io/trygvis/cj/Explorer.scala b/src/main/scala/io/trygvis/cj/Explorer.scala index 9c4206f..5089d1f 100644 --- a/src/main/scala/io/trygvis/cj/Explorer.scala +++ b/src/main/scala/io/trygvis/cj/Explorer.scala @@ -1,8 +1,10 @@ package io.trygvis.cj import scala.collection.JavaConversions._ +import scala.util.Properties +import scala.util.control.Exception._ import scala.io.Source -import java.net.{URLEncoder, HttpURLConnection, URI} +import java.net.{URL, URLEncoder, HttpURLConnection, URI} import java.io._ import javax.servlet.http.HttpServletRequest import unfiltered.request._ @@ -10,7 +12,8 @@ import unfiltered.response._ import unfiltered.filter._ import unfiltered.jetty._ -case class CjResponse(code: Int, status: String, headers: java.util.Map[String, java.util.List[String]]) +case class CjRequest(urlString: String, url: Option[URL], method: String, headers: Option[Map[String, java.util.List[String]]]) +case class CjResponse(code: Int, status: String, headers: java.util.Map[String, java.util.List[String]], content: Option[String]) class Browser extends Plan { import unfiltered.directives._, Directives._ @@ -26,7 +29,8 @@ class Browser extends Plan { new Views(uri) } - def queryParam(name: String) = Directive[Any, Any, String]({ request: HttpRequest[Any] => + def queryParam(name: String) = Directive[Any, Any, String]({ + request: HttpRequest[Any] => request.parameterValues(name).headOption match { case Some(value) => Result.Success(value) @@ -51,9 +55,10 @@ class Browser extends Plan { params <- QueryParams views = viewsX(r) } yield { - println("url=" + url) + val method = Option(r.getParameter("method")).filter {_.nonEmpty} getOrElse "GET" + println("Request: " + method + " " + url) - val targetParams = params flatMap { + val collectionParams = params flatMap { case (key, value) if key.startsWith("param-") => // It makes no sense to have duplicate query keys so just pick the first one. This is most likely the // same as most web framework does when given multiple query parameters. @@ -62,30 +67,52 @@ class Browser extends Plan { None } - println("targetParam=" + targetParams) + val q = collectionParams map { + case (key, value) => + URLEncoder.encode(key, "utf-8") + "=" + URLEncoder.encode(value, "utf-8") + } + + val collectionUrlString = url + (if (q.nonEmpty) "?" + q.reduce(_ ++ "&" ++ _) else "") + val collectionUrl = allCatch opt {URI.create(collectionUrlString).toURL } filter {_.getProtocol.startsWith("http")} - val q = targetParams map { case (key, value) => - URLEncoder.encode(key, "utf-8") + "=" + URLEncoder.encode(value, "utf-8") + val conO = collectionUrl map { u: java.net.URL => + val c = u.openConnection().asInstanceOf[HttpURLConnection] + c.setRequestProperty("Accept", "application/vnd.collection+json") + c.setUseCaches(false) + c } - println("q=" + q) + val response = conO map { c => allCatch either { + c.connect() + val headers = c.getHeaderFields.toMap filter { case (key, value) => key != null } + val content = allCatch opt Source.fromInputStream(c.getInputStream, "utf-8").mkString("") + + c.disconnect() + + CjResponse(c.getResponseCode, c.getResponseMessage, headers, content) + }} - val uri = URI.create(url + (if(q.nonEmpty) "?" + q.reduce (_ ++ "&" ++ _) else "")) - println("uri=" + uri) + val requestHeaders = conO map { con => + con.getRequestProperties.toMap filter { case (key, value) => !value.isEmpty && value.get(0) != null } + } + + val collection = response match { + case Some(Right(CjResponse(_, _, _, Some(content)))) => + Some(Collection.parseCollection(new StringReader(content))) + case _ => None + } - val con = uri.toURL.openConnection().asInstanceOf[HttpURLConnection] - con.setRequestProperty("accept", "application/vnd.collection+json") - val content = Source.fromInputStream(con.getInputStream, "utf-8").mkString("") - val headers = con.getHeaderFields.toMap filter {case (key, _) => key != null} - val result = Collection.parseCollection(new StringReader(content)) - Ok ~> Html5(views.data(uri, targetParams, result, CjResponse(con.getResponseCode, con.getResponseMessage, headers))) + Ok ~> Html5(views.data(CjRequest(collectionUrlString, collectionUrl, method, requestHeaders), response, collectionParams, collection)) } } } } object Explorer extends App { - Http(8080). + val port = Properties.envOrElse("PORT", "8080").toInt + println("Starting on port:" + port) + + Http(port). plan(new Browser). resources(getClass.getResource("/public/")). run() diff --git a/src/main/scala/io/trygvis/cj/Views.scala b/src/main/scala/io/trygvis/cj/Views.scala index b4429d8..8b3e5d9 100644 --- a/src/main/scala/io/trygvis/cj/Views.scala +++ b/src/main/scala/io/trygvis/cj/Views.scala @@ -1,10 +1,12 @@ package io.trygvis.cj import scala.collection.JavaConversions._ -import scala.xml.{Group, NodeSeq, Elem} -import java.net.{MalformedURLException, URL, URI, URLEncoder} +import scala.xml.{NodeSeq, Elem} +import java.net._ import org.json4s.native.JsonMethods import java.io.{PrintWriter, StringWriter, Writer} +import scala.xml.Group +import scala.Some class Views(baseUrl: String) { @@ -120,8 +122,8 @@ class Views(baseUrl: String) { layout(content, None) } - def data(url: URI, targetParams: Map[String, String], result: Either[Throwable, Collection], res: CjResponse) = { - + def data(request: CjRequest, response: Option[Either[Throwable, CjResponse]], + targetParams: Map[String, String], collection: Option[Either[Throwable, Collection]]) = { def href(uri: URI) = { // val splits = uri.getPath.split('/') // for split in splits @@ -413,26 +415,66 @@ class Views(baseUrl: String) { } - def httpResponse = { + def httpRequest(req: CjRequest) = {
-
-
Request URL
-
{url}
-
- - - - - {res.headers map { case (header, values) => { values map { value => - - - - - }}}} -
{res.code} {res.status}
{header}{tryLink(value)}
+ {req.url match { + case None => +
+
Could not parse generated URL
+
{req.urlString}
+
+ case _ => + }} + { + req.headers match { + case None => + case Some(headers) => +

Request

+ + + + + {headers map { case (header, values) => { values map { value => + + + + + }}}} +
{req.method} {req.url map { url => {url.toExternalForm} } getOrElse req.urlString}
{header}: {tryLink(value)}
+ }}
} + def httpResponse(r: Either[Throwable, CjResponse]) = { r match { + case Left(ex) => + if (ex.isInstanceOf[ConnectException]) { + "Unable to connect: " + ex.getMessage + } else if (ex.isInstanceOf[UnknownHostException]) { + "Unknown host: " + ex.getMessage + } else { + +

Unknown error while connecting to remote server

+
{getStackTrace(ex)}
+
+ } + case Right(res) => +
+ + + + + + {res.headers map { case (header, values) => { values map { value => + + + + + }}}} +
{res.code} {res.status}
{header}: {tryLink(value)}
+
+
+ }} + def parsedContent(implicit cj: Collection) =