summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2013-06-02 23:20:00 +0200
committerTrygve Laugstøl <trygvis@inamo.no>2013-06-02 23:20:00 +0200
commit131e5ec53ad065461c760c16f68af2522437928d (patch)
tree727428f6c8a8a29204a09aedb2bd447981ebbf76 /src/main
parentc7b619402f290fdf77312af5f426eb6bd0d5e5f7 (diff)
downloadcollection-json-explorer-scala-131e5ec53ad065461c760c16f68af2522437928d.tar.gz
collection-json-explorer-scala-131e5ec53ad065461c760c16f68af2522437928d.tar.bz2
collection-json-explorer-scala-131e5ec53ad065461c760c16f68af2522437928d.tar.xz
collection-json-explorer-scala-131e5ec53ad065461c760c16f68af2522437928d.zip
o Tried to improve error handling when the backend server fails.HEADmaster
Diffstat (limited to 'src/main')
-rw-r--r--src/main/scala/io/trygvis/cj/Explorer.scala63
-rw-r--r--src/main/scala/io/trygvis/cj/Views.scala130
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) {
</div>
}
- def httpResponse = {
+ def httpRequest(req: CjRequest) = {
<div class="row-fluid">
- <dl>
- <dt>Request URL</dt>
- <dd><a href={render(url)}>{url}</a></dd>
- </dl>
- <table>
- <tr>
- <td colspan="2">{res.code} {res.status}</td>
- </tr>
- {res.headers map { case (header, values) => { values map { value =>
- <tr>
- <td><tt>{header}</tt></td>
- <td><tt>{tryLink(value)}</tt></td>
- </tr>
- }}}}
- </table>
+ {req.url match {
+ case None =>
+ <dl>
+ <dt>Could not parse generated URL</dt>
+ <dd>{req.urlString}</dd>
+ </dl>
+ case _ =>
+ }}
+ {
+ req.headers match {
+ case None =>
+ case Some(headers) =>
+ <h2>Request</h2>
+ <table>
+ <tr>
+ <td colspan="2"><tt>{req.method} {req.url map { url => <a href={render(url.toURI)}>{url.toExternalForm}</a> } getOrElse req.urlString}</tt></td>
+ </tr>
+ {headers map { case (header, values) => { values map { value =>
+ <tr>
+ <td><tt>{header}: </tt></td>
+ <td><tt>{tryLink(value)}</tt></td>
+ </tr>
+ }}}}
+ </table>
+ }}
</div>
}
+ 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 {
+ <xml:group>
+ <p>Unknown error while connecting to remote server</p>
+ <pre>{getStackTrace(ex)}</pre>
+ </xml:group>
+ }
+ case Right(res) =>
+ <div class="row-fluid">
+ <tt>
+ <table>
+ <tr>
+ <td colspan="2">{res.code} {res.status}</td>
+ </tr>
+ {res.headers map { case (header, values) => { values map { value =>
+ <tr>
+ <td>{header}: </td>
+ <td>{tryLink(value)}</td>
+ </tr>
+ }}}}
+ </table>
+ </tt>
+ </div>
+ }}
+
def parsedContent(implicit cj: Collection) = <xml:group>
<section id="meta">
<div class="page-header">
@@ -477,13 +519,14 @@ class Views(baseUrl: String) {
</xml:group>
def innerContent = <xml:group>
- {result match {
- case Left(ex) =>
+ {collection match {
+ case None =>
+ case Some(Left(ex)) =>
<section id="server-error">
<div class="page-header"><h1>Server Error</h1></div>
<pre>{getStackTrace(ex)}</pre>
</section>
- case Right(cj) => <xml:group>
+ case Some(Right(cj)) => <xml:group>
{try { parsedContent(cj) } catch { case ex: Exception => <div>Unable to process model: {ex.getMessage}</div> }}
<section id="formatted-body">
<div class="page-header"><h1>Formatted Body</h1></div>
@@ -495,23 +538,32 @@ class Views(baseUrl: String) {
</section>
</xml:group>
}}
- <section id="http-response">
- <div class="page-header"><h1>HTTP Response</h1></div>
- {httpResponse}
+ <section id="http-request">
+ <div class="page-header"><h1>HTTP Request</h1></div>
+ {httpRequest(request)}
</section>
+ {response match {
+ case None =>
+ case Some(some) =>
+ <section id="http-response">
+ <div class="page-header"><h1>HTTP Response</h1></div>
+ {httpResponse(some)}
+ </section>
+ }}
</xml:group>
def sidebar = <div id="navbar" class="sidebar-nav sidebar-nav-fixed">
<ul class="nav nav-list">
- {result match {
- case Left(_) =>
+ {collection match {
+ case None =>
+ case Some(Left(_)) =>
<li class="nav-header"><a href="#server-error">Server Error</a></li>
- case Right(cj) =>
+ case Some(Right(cj)) =>
{try {
<xml:group>
<li class="nav-header active"><a href="#meta">Meta</a></li>
{cj.links.zipWithIndex map { case (l, i) =>
- <li><a href={"#link-" + (i + 1)}>{getName(l, "Link", i + 1)}</a></li>
+ <li><a href={"#link-" + (i + 1)}>{getName(l, "Link ", i + 1)}</a></li>
}}
{if(cj.items.nonEmpty) {
<xml:group>
@@ -532,10 +584,26 @@ class Views(baseUrl: String) {
{cj.template map { _ => <li class="nav-header"><a href="#template">Template</a></li> } getOrElse NodeSeq.Empty }
{cj.error map { _ => <li class="nav-header"><a href="#error">Error</a></li> } getOrElse NodeSeq.Empty }
<li class="nav-header"><a href="#formatted-body">Formatted Body</a></li>
- <li class="nav-header"><a href="#http-response">HTTP Response</a></li>
</xml:group>
} catch { case ex: Exception => <div>Unable to process model: {ex.getMessage}</div> }}
}}
+ <li class="nav-header"><a href="#http-request">HTTP Request</a></li>
+ {
+ response match {
+ case None =>
+ case Some(some) =>
+ <li class="nav-header"><a href="#http-response">HTTP Response</a></li>
+ /*
+ <xml:group>
+ <li class="nav-header"><a href="#http-response">HTTP Response</a></li>
+ {some match {
+ case Right(_) =>
+ <li class="nav-header"><a href="#formatted-body">Formatted Body</a></li>
+ case _ =>
+ }}
+ </xml:group>
+ */
+ }}
</ul>
</div>