Dienstag, 5. November 2013

Files hosted on github

Hi guys!

I created a Github-Repository which will hold all files published/described on my blog.
This should help you guys downloading/using the stuff.

Have fun!

PHP-Proxy for Carte

Hi again!
my first post was dealing with a JSP-based proxypage to enable "crosssite javascripting" access to the Carte-Service.
Now it´s time to serve the same approach for the PHP-folks out there.
Basically, all you need is:
  • PHP (I used version 5.5.4, but any v5 should be good I think)
  • curl-Extension activated in your PHP (please refer to php.net for details)
As the details already had been discussed in the first post, I will now just paste the code for the proxy.php file which will do the same "proxy magic":
<?
$url = $_GET["url"];
$url .= "?1=1";
foreach ( $_GET as $getKey => $getVar ) {
  //print $getVar;
  if ( $getKey != "url" ) {
    $url .= "&".$getKey."=".str_replace(" ","+",urlencode($getVar));
  }
}
// tested with such urls:
// http://localhost/test/proxy.php?url=http://localhost:8888/kettle/transStatus/&name=Row+generator+test&id=ee8d2d9d-da0a-404c-8488-ededf6b176df&xml=y
//echo $url;
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_USERPWD, "cluster:cluster");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

$response = curl_exec($curl);
$content = curl_getinfo($curl,CURLINFO_CONTENT_TYPE);
header("Content-type: ".$content);
// pretty ugly but I could not come up with a simpler approach to separate the HTTP-Headers from the Content
$response_arr = explode("\r\n",$response);
$startresponse = false;
$responsetext = "";
foreach (  $response_arr as $line ) {
  if ( trim($line) == "" ) {
    $startresponse = true;
  }
  if ( $startresponse ) {
    $responsetext .= $line;
  }
}
echo $responsetext;
?>


To download the file directly, head over to github: proxy.php

Please mind the linebreaks when downloading/installing the file.
Anyway, I hope this file will satisfy the PHP-guys and be of some use for you out there!
Thanks for reading, see you next time.

Mittwoch, 9. Oktober 2013

Carte and Crosssite-Scripting? Come on!

Welcome to my very first blog-post!

I would like to start off with a short "Thank you" to all of the Pentaho OpenSource-community
out there. Being a very responsive and supportive community I experienced a warm and healthy
environment ever since starting using the Pentaho BI-Suite (PDI, BI-Server and many of the infamous plugins) about 6 years ago.

This being said I would like to dive straight ahead into the post...


Lately I came across an intersting situation which this blogpost will talk about:
Making PDI/Carte-"services" available for other websites.

In short, this post will assume you to have some basic knowledge about:
  • jQuery/AJAX requests/responses
  • PDI and the Carte-slaveserver

To start, I would like to describe my intial problem, then display the solution I came up with while
being on-site at the customer.
As almost always, there´s many ways to solve a given problem, so please don't be picky about me if you and your solution are fit better for your needs.

Ok, now let´s go!

Problem
My situation was, to have the Carte-Service running on a given computer with also having a JBoss AS server deployed on another (or maybe the same) machine.
So we end up with Carte listening on http://localhost:8888 (Basic-Authentication active) and the
JBoss listening on http://localhost:8080 and having a bunch of apps deployed on the JBoss.

From inside one of these apps my goal was to access some KTRs on Carte via Web (e.g.: http://localhost:8888/kettle/executeTrans/?trans=getMeSomeData.ktr)
and return the data from the KTR to the requesting jQuery/AJAX call, which basically is something like this:

  $.ajax({
    url : 'http://localhost:8888/kettle/executeTrans/?trans=getMeSomeData.ktr',
    method : 'GET',
    success: function(data) {
      doSomethingWithTheData(data)
    }
  })

 
However, having the originating request coming from localhost:8080 and requesting data from localhost:8888 causes an obvious problem:
"Crosssite scripting"!
Thus, jQuery requires you to use "CORS/JSONP" which just did not work for me.


Solution

So I ended up thinking about another approachand "proxy'ing" came to my mind.
In the end, I did some research and since I did not want to setup proxy-rules inside my JBoss I was looking for a more "light-weight" proxy.

Now this is my result:

proxy.jsp:
<%@ page
    import="java.io.*,
            java.util.*,
            java.net.*"
    contentType="text/plain; charset=UTF-8"
%><%
StringBuffer sbf = new StringBuffer();
//Access the page
try {
  // Add additional parameters to the requested url
  String requestedUrl = request.getParameter("url");
  Map<String,String> additionalParameters = request.getParameterMap();
  Iterator parameterIterator = additionalParameters.keySet().iterator();
  while( parameterIterator.hasNext() ) {
    String nextParamName = (String) parameterIterator.next();
    // no need to repeat "url" as this is our first and really mandatory request parameter
    if ( !"url".equals(nextParamName) ) {
      requestedUrl += "&"+nextParamName+"="+request.getParameter(nextParamName);
    }
  }
  requestedUrl = requestedUrl.replace(" ","+");
  URL url = new URL(requestedUrl);
  HttpURLConnection conn=(HttpURLConnection)url.openConnection();
  conn.setRequestMethod("GET");
  String authHeaderValue = "Basic Y2x1c3RlcjpjbHVzdGVy";
  conn.setRequestProperty("Authorization",authHeaderValue);
  // Copy the proxied content-type to own response
  response.setContentType(conn.getContentType());
  BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
  String inputLine;
  while ( (inputLine = in.readLine()) != null) {
    sbf.append(inputLine);
  }
  in.close();
} catch (MalformedURLException e) {
} catch (IOException e) {
}
%><%= sbf.toString()%>


To get the file directly, you can download it at github: proxy.jsp

After creating this JSP and putting it into the folder of one of the JBoss apps I registered this proxy-servlet inside the
web.xml of an existing app (in the example inside the "myapp" as to be seen in the jQuery-call below)

    <servlet>
      <servlet-name>proxy</servlet-name>
      <jsp-file>/proxy.jsp</jsp-file>
   </servlet>
   <servlet-mapping>
      <servlet-name>proxy</servlet-name>
      <url-pattern>/proxy</url-pattern>
   </servlet-mapping>

  
After re-deploying the JBoss app I was then able to use the Carte-service by using this kind of AJAX-Call:

  $.ajax({
    url : '/myapp/proxy?url=http://localhost:8888/kettle/executeTrans/?trans=getMeSomeData.ktr',
    method : 'GET',
    success: function(data) {
      doSomethingWithTheData(data)
    }
  })


...which seems to be a more flexible and reuseable way to me of creating such proxy-requests without having to change the server-setup extensively.

Notes:
  • Please note that the default "cluster//cluster" credentials for the Carte-Service are "hard-coded" inside the proxy.jsp on line String authHeaderValue = "Basic Y2x1c3RlcjpjbHVzdGVy"; So if you need to/had changed that, check out http://decodebase64.com/ for a convenient site to create the new hash (enter them in the following format: <username>:<password>, e.g: cluster:cluster)

Upcoming in the next post I'd like to recreate the same approach for the PHP-Guys out there who would like to use Carte and PDI as JSON-Datasource in their PHP-applications.
Thanks for reading and best regards,

Tom

[Update]
The internet is great! Just some 16 hours after posting this blog I received feedback from another Pentaho-integrator (yes, that´s you Harris! ;-) ) claiming that the proxy did obviously default to "text/plain" as Content-Type, removing the possibility to properly deal with Carte's own "Status-Interfaces" (which are XML-Responses)
So now I am happy to present you version2 of my proxy.jsp, now with parameter and Content-Type support.