diff options
-rw-r--r-- | src/main/scala/io/trygvis/cj/Explorer.scala | 63 | ||||
-rw-r--r-- | 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) { </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> |