JAVAOBJ webio

From sasCommunity
Jump to: navigation, search

A SAS-L thread discussed Proc HTTP. I have 9.2 phase 1, and no such procedure is found.

The poster had Proc HTTP in SAS version 9.2 phase 2, so I speculate it starts then. The documentation for Proc HTTP is found here.

One of the strongest features of Proc HTTP is the ability to deliver POST parameters to a URL.

For SAS users without Proc HTTP, but with JavaObj, there is a solution and it's right here! Additionally, this solution be easily extended and improved since you have the source code -- not the case with Proc HTTP.

Concept

An adapter Java class named webio was written to allow the Java URL class to be utilized from SAS DATA Step via the JABAOBJ Component Object. See SAS documentation for more details on JAVAOBJ.

webio has methods for setting both GET and POST parameter name value pairs, which are communicated to a target URL when a connection is made. There are methods for reading the URL results (the raw HTML or what ever content-type the URL emits).

webio has methods for delivering proxy and domain user/pass info.

In its current form there are no methods for dealing with cookies, setting headers, or reading response headers.

webio source code

This code was developed using Eclipse 3.4.2. You can download the precompiled jar with this link Media:Webio.jar.zip. See the sample SAS program at the bottom of the article for an explanation on how to use the jar.

If you experience any problems, have any questions or find any bugs, please contact Richard A. DeVenezia.

webio

package com.devenezia.sas;
 
import java.io.*;
import java.net.*;
import java.util.*;
 
public class webio  {
  private ArrayList<String> getNames = new ArrayList<String>();
  private ArrayList<String> getValues = new ArrayList<String>();
 
  private ArrayList<String> postNames = new ArrayList<String>();
  private ArrayList<String> postValues = new ArrayList<String>();
 
  private String spec;
 
  private URLConnection urlConn;
  private String basicProxyAuth;
  private String basicDomainAuth;
 
  private BufferedReader in;
  private boolean endOfContent = false;
 
  public String getVersionNumber ()
  {
	  return "1.0";
  }
 
  public void resetParameters ()
  {
	  getNames.clear();
	  getValues.clear();
	  postNames.clear();
	  postValues.clear();
  }
 
  public webio ()
  {
	  resetParameters ();
  }
 
  public webio (String aspec)
  {
	  spec = aspec;
	  resetParameters ();
  }
 
  public void setSpec (String aspec)
  {
	  spec = aspec;
  }
 
  public String getSpec ()
  {
	  return spec;
  }
 
  public void setHttpProxyHost (String host)
  {
	  System.getProperties().put("http.proxyHost", host);
  }
    public void setHttpProxyPort (String port)
  {
	  System.getProperties().put("http.proxyPort", port);
  }
  public void setHttpsProxyHost (String host)
  {
	  System.getProperties().put("https.proxyHost", host);
  }
  public void setHttpsProxyPort (String port)
  {
	  System.getProperties().put("https.proxyPort", port);
  }
  public void setBasicProxyAuthorization (String user, String pass)
  {
	  basicProxyAuth = Base64Transformation.encodeString(user+":"+pass);
  }
  public void setBasicDomainAuthorization (String user, String pass)
  {
	  basicDomainAuth = Base64Transformation.encodeString(user+":"+pass);
  }
 
 
  public void setGet (String name, String value)
  {
	  getNames.add(name.trim());
	  getValues.add(value.trim());
  }
 
  public String getGet (double ix)
  {
	  int i = (int) ix;
 
	  if (i < getNames.size())
	    return getNames.get(i) + "=" + getValues.get(i);
	  else
		return "";
  }
 
  public void setPost (String name, String value)
  {
	  postNames.add(name.trim());
	  postValues.add(value.trim());
  }
 
  public String getPost (double ix)
  {
	  int i = (int) ix;
	  if (i < postNames.size())
	    return postNames.get(i) + "=" + postValues.get(i);
	  else
		return "";
  }
 
  public String getGetData ()
  {
	  StringBuffer result = new StringBuffer();
 
	  for (int i=0;i<getNames.size();i++)
	  {
		  if (i>0) result.append('&');
		  result.append (getNames.get(i) + "=" + getValues.get(i));
	  }
 
	  return result.toString();
  }
 
  public String getPostData ()
  {
	  StringBuffer result = new StringBuffer();
 
	  for (int i=0;i<postNames.size();i++)
	  {
		  if (i>0) result.append('&');
		  result.append (postNames.get(i) + "=" + postValues.get(i));
	  }
 
	  return result.toString();
  }
 
  public void doConnect () throws MalformedURLException, IOException 
  {
	  String getString = getGetData();
	  String postString = getPostData();
 
	  URL url;
 
	  if (getString.length() > 0)
	    if (spec.contains("?")) 
	      spec += "&" + getString;
	    else
	      spec += "?" + getString;
 
	  url = new URL (spec);
	  urlConn = url.openConnection();
 
	  if (basicProxyAuth != null) {
		 urlConn.setRequestProperty ("Proxy-Authorization", "Basic " + basicProxyAuth); 
	  }
 
	  if (basicDomainAuth != null) {
		  urlConn.setRequestProperty ("Authorization", "Basic " + basicDomainAuth);
	  }
 
	  if (postString.length() > 0)
	  {
		  urlConn.setDoOutput (true);
 
		  OutputStreamWriter out = new OutputStreamWriter(urlConn.getOutputStream());
		  out.write(postString);
		  out.close();
	  }
  }
 
  public String getContentLine() throws Exception {
	  if (in == null) 
	  {
		in = new BufferedReader(
		     new InputStreamReader(
			 urlConn.getInputStream()));
	  }
 
	  String contentLine;
	  contentLine = in.readLine();
 
	  endOfContent = (contentLine == null);
 
	  return contentLine;
  }
 
  public boolean getIsEndOfContent ()
  {
	  return endOfContent;
  }
}

Base64Transformation

package com.devenezia.sas;
 
/**
* A Base64 Encoder/Decoder.
*
* <p>
* This class is used to encode and decode data in Base64 format as described in RFC 1521.
*
* <p>
* This is "Open Source" software and released under the <a href="http://www.gnu.org/licenses/lgpl.html">GNU/LGPL</a> license.<br>
* It is provided "as is" without warranty of any kind.<br>
* Copyright 2003: Christian d'Heureuse, Inventec Informatik AG, Switzerland.<br>
* Home page: <a href="http://www.source-code.biz">www.source-code.biz</a><br>
*
* <p>
* Version history:<br>
* 2003-07-22 Christian d'Heureuse (chdh): Module created.<br>
* 2005-08-11 chdh: Lincense changed from GPL to LGPL.<br>
* 2006-11-21 chdh:<br>
*  &nbsp; Method encode(String) renamed to encodeString(String).<br>
*  &nbsp; Method decode(String) renamed to decodeString(String).<br>
*  &nbsp; New method encode(byte[],int) added.<br>
*  &nbsp; New method decode(String) added.<br>
*/
 
public class Base64Transformation {
 
// Mapping table from 6-bit nibbles to Base64 characters.
private static char[]    map1 = new char[64];
   static {
      int i=0;
      for (char c='A'; c<='Z'; c++) map1[i++] = c;
      for (char c='a'; c<='z'; c++) map1[i++] = c;
      for (char c='0'; c<='9'; c++) map1[i++] = c;
      map1[i++] = '+'; map1[i++] = '/'; }
 
// Mapping table from Base64 characters to 6-bit nibbles.
private static byte[]    map2 = new byte[128];
   static {
      for (int i=0; i<map2.length; i++) map2[i] = -1;
      for (int i=0; i<64; i++) map2[map1[i]] = (byte)i; }
 
/**
* Encodes a string into Base64 format.
* No blanks or line breaks are inserted.
* @param s  a String to be encoded.
* @return   A String with the Base64 encoded data.
*/
public static String encodeString (String s) {
   return new String(encode(s.getBytes())); }
 
/**
* Encodes a byte array into Base64 format.
* No blanks or line breaks are inserted.
* @param in  an array containing the data bytes to be encoded.
* @return    A character array with the Base64 encoded data.
*/
public static char[] encode (byte[] in) {
   return encode(in,in.length); }
 
/**
* Encodes a byte array into Base64 format.
* No blanks or line breaks are inserted.
* @param in   an array containing the data bytes to be encoded.
* @param iLen number of bytes to process in <code>in</code>.
* @return     A character array with the Base64 encoded data.
*/
public static char[] encode (byte[] in, int iLen) {
   int oDataLen = (iLen*4+2)/3;       // output length without padding
   int oLen = ((iLen+2)/3)*4;         // output length including padding
   char[] out = new char[oLen];
   int ip = 0;
   int op = 0;
   while (ip < iLen) {
      int i0 = in[ip++] & 0xff;
      int i1 = ip < iLen ? in[ip++] & 0xff : 0;
      int i2 = ip < iLen ? in[ip++] & 0xff : 0;
      int o0 = i0 >>> 2;
      int o1 = ((i0 &   3) << 4) | (i1 >>> 4);
      int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
      int o3 = i2 & 0x3F;
      out[op++] = map1[o0];
      out[op++] = map1[o1];
      out[op] = op < oDataLen ? map1[o2] : '='; op++;
      out[op] = op < oDataLen ? map1[o3] : '='; op++; }
   return out; }
 
/**
* Decodes a string from Base64 format.
* @param s  a Base64 String to be decoded.
* @return   A String containing the decoded data.
* @throws   IllegalArgumentException if the input is not valid Base64 encoded data.
*/
public static String decodeString (String s) {
   return new String(decode(s)); }
 
/**
* Decodes a byte array from Base64 format.
* @param s  a Base64 String to be decoded.
* @return   An array containing the decoded data bytes.
* @throws   IllegalArgumentException if the input is not valid Base64 encoded data.
*/
public static byte[] decode (String s) {
   return decode(s.toCharArray()); }
 
/**
* Decodes a byte array from Base64 format.
* No blanks or line breaks are allowed within the Base64 encoded data.
* @param in  a character array containing the Base64 encoded data.
* @return    An array containing the decoded data bytes.
* @throws    IllegalArgumentException if the input is not valid Base64 encoded data.
*/
public static byte[] decode (char[] in) {
   int iLen = in.length;
   if (iLen%4 != 0) throw new IllegalArgumentException ("Length of Base64 encoded input string is not a multiple of 4.");
   while (iLen > 0 && in[iLen-1] == '=') iLen--;
   int oLen = (iLen*3) / 4;
   byte[] out = new byte[oLen];
   int ip = 0;
   int op = 0;
   while (ip < iLen) {
      int i0 = in[ip++];
      int i1 = in[ip++];
      int i2 = ip < iLen ? in[ip++] : 'A';
      int i3 = ip < iLen ? in[ip++] : 'A';
      if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
         throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
      int b0 = map2[i0];
      int b1 = map2[i1];
      int b2 = map2[i2];
      int b3 = map2[i3];
      if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
         throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
      int o0 = ( b0       <<2) | (b1>>>4);
      int o1 = ((b1 & 0xf)<<4) | (b2>>>2);
      int o2 = ((b2 &   3)<<6) |  b3;
      out[op++] = (byte)o0;
      if (op<oLen) out[op++] = (byte)o1;
      if (op<oLen) out[op++] = (byte)o2; }
   return out; }
 
// Dummy constructor.
private Base64Transformation() {}
}

Test SAS Program

Your SAS session must know where the java class com.devenezia.sas.webio resides. Depending on the version of SAS you are running, you will need to:

  • set environment variable CLASSPATH
  • set SAS startup option JREOPTIONS(-Djava.class.path=...)

webio-test.sas

data _null_;
  length s $32767;
 
  declare javaobj j('com.devenezia.sas.webio');
  j.callVoidMethod ('setSpec', 'http://www.devenezia.com/prot/submit.php');
 
  * set this to your http debugging proxy, or actual proxy;
  j.CallVoidMethod ('setHttpProxyHost', 'localhost');
  j.CallVoidMethod ('setHttpProxyPort', '6301');
 
  j.CallVoidMethod ('setBasicDomainAuthorization','richard','example');
 
  length name $20 value $200;
 
  do i = 1 to 10;
    name = byte(64+i);
    value = cat(i**2);
    j.callVoidMethod('setGet',name,value);
/* debug */
    j.callStringMethod('getGetData',s);
    put 'get ' i= s=;
/**/
  end;
 
  do i = 1 to 10;
    name = 'bar_' || byte(64+i);
    value = cat(i**3);
    j.callVoidMethod('setPost',name,value);
/* debug */
    j.callStringMethod('getPostData',s);
    put 'post ' i= s=;
/**/
  end;
 
/* debug * /
  length pair $200;
  do i = 0 by 1;
    j.callStringMethod('getGet',i,pair);
    if pair = '' then leave;
    put 'get ' i= pair=;
  end;
 
  do i = 0 by 1;
    j.callStringMethod('getPost',i,pair);
    if pair = '' then leave;
    put 'post ' i= pair=;
  end;
/**/
 
  j.callVoidMethod ('doConnect');
 
  j.callStringMethod ('getContentLine', s);
  j.callBooleanMethod ('getIsEndOfContent', eoc);
 
  do while (not eoc);
    put s=;
    j.callStringMethod ('getContentLine', s);
    j.callBooleanMethod ('getIsEndOfContent', eoc);
  end;
 
  j.delete();
 
  stop;
run;