程序员可能会经常碰到这样的事情:建立一个servlet应用程序,它与公司的数据库相连接,为客户提供一种特定的服务,这个应用程序受到一个强大的验证机制保护,全世界有成千上万的客户都在使用它。现在就出现了一个问题:当应用程序处在公司的防火墙之外时,你将如何从应用程序提供用户对数据库的访问?你知道,网络管理员是不会专门为你的应用程序与数据库相连接而打开一个特殊端口的。
HTTP隧道技术和XML
如何越过防火墙与客户/服务器应用程序相连接这个问题已经困扰程序员很久了。在多数情况下,一个公司的防火墙总是尽可能少地打开端口。一般情况下,你能够使用的唯一端口就是80,这也就是Web所使用的端口。
解决这个问题的方法就是使用HTTP隧道技术(HTTP tunneling)。这个方法首先将请求包装在一个HTTP POST请求中,然后这个请求再由一个在防火墙内的Web 服务器上的CGI 应用程序(例如一个servlet)来处理。
Servlet恢复原始的请求,执行它,然后将结果插入到HTTP响应流中。防火墙将这种相互作用解释为对一个 Web页面的常规请求,并允许对它继续进行处理。这就象特洛伊木马一样:看起来是一个普通的请求,但其中隐藏着预料不到的负载。
下一个问题是如何对请求进行格式化?当然,使用XML是将这些负载送入一个HTTP请求中去的最佳选择。这个观念很有独创性,HTTP之上的XML是一个热门的新兴领域,一些新的规则正在编写中,将来可以成为分布式应用程序的标准通讯协议。其中,简单对象访问协议(SOAP)是最得到公认的。
遗憾的是,现在还没有一个稳定执行的SOAP供我们使用,目前所能找到的最好的一个来自Apache集团,但是它只支持简单的返回类型。因此,它对于我们的项目是没有用的。但是这也不错,这意味着我们可以提出自己的HTTP上的XML的协议,并且借此来学习其中包含的概念。
概念
现在我们来建立一个简单的框架结构(framework),它将HTTP上的XML作为基本的通讯策略,从而让我们能够创建一套服务,而且使得这些服务从分布在Internet上四面八方的桌面应用程序都可以进行访问。
首先,我们需要建立普通请求和响应的语法。请求看起来是这样的:
<?xml version='1.0' encoding='utf-8' ?>
<http-request>
<requestType>
[type of request]
</requestType>
<request>
[Application specific request.This will be an XML Elment ]
</request>
</http-request>
响应看起来是这样的:
<?xml version='1.0' encoding='utf-8' ?>
<response>
<responseMessage>
[the response Message]
</responseCode>
<responseCode>
[an application specific return code.]
</responseCode>
<response>
[Application specific request.This will be an XML Element]
</response>
</http-response>
为了理解这个框架结构背后的概念,我们要编写一个应用服务例程:一个简单的数据库服务,它对任何SQL语句进行处理,并且将结果作为一个向量来返回。请求的细节,也就是请求元素所包含的XML标记非常简单,如下:
<sql-statement>
[The SQL statement to be executed]
</ sql-statement >
响应结果是这样的:
<result-set>
</result-count>
[the number of rows in the result set]
</result-count>
<row>
<col name='name'>
[the value]
</col>
?
</row>
?
<result-set>
HTTPService是一个servlet,它响应一个POST请求,恢复XML负载,并使用它来创建一个ServiceRequest例示。然后,根据请求的类型,将请求交给HttpServiceHandler 抽象类的一个特定子类。Handler类执行请求,在ServiceResponse的一个例示中存储结果,然后将这个结果发送回客户端应用程序。
通过按照惯例为服务处理器类命名,我们可以利用Java的映象功能来创建处理器类的一个例示,这个处理器类仅仅是建立在ServiceRequest 对象的服务类型属性的基础上。这就取消了HttpService和所有服务处理器类之间的依赖性,从而意味着当我们增加一个新服务时,不再需要改变 HttpService类。
在我们这个例子中,服务的类型是DBService,因此我们将创建HttpServiceHandler的一个子类,叫做DBServiceHandler。在HttpService中,我们使用以下代码:
String className = PACKAGE_NAME + "." + request.getRequestType() + "Handler";
HttpServiceHandler handler = Class.fromName(className).newInstance();
handler.handleRequest(request);
一个HttpServiceHandler子类需要执行一个方法processRequest(),它需要取一个ServiceRequest对象,然后返回一个ServiceResponse对象。这个方法是在handleRequest 方法的过程中由子类调用的:
Public void handleRequest(ServiceRequest request)
{
Serviceresponse response = processRequest(request);
SendResponse(response);
}
这是使用Template(模板)方法模式的一个典型例子,在这个过程中,抽象超类调用在一个子类中执行的方法。
ServiceRequest类将服务特定数据存储为一个XML 文档。这个类由访问者来设置和获取请求类型,它还有方法来处理请求的细节。getRequest()方法返回包含在请求标记中的XML节点,setRequest()方法则用一个新生成的请求来覆盖原来的请求。这个类还使用两个factory方法,创建新的元素和文本节点,允许开发人员生成新的请求。ServiceResponse类以一种非常简单的方法来处理请求的细节。
虽然这两个类允许我们对所有类型的请求和响应进行处理,但是开发人员也必须要了解每个请求的特殊语法。开发人员不能对请求的格式是否正确进行任何确认。
为了简化这个过程,我们将创建ServiceRequest和 ServiceResponse的子类,它们分别叫做DBServiceRequest和DBServiceResponse,它们都有处理服务特定细节的方法。例如,DBServiceRequest有设置和获取SQL 语句的方法,同时 DBServiceResponse有设置和获取结果记数值和结果设置矢量的方法。
服务是用HttpServiceClient类来访问的。在客户端应用程序中,有以下的代码:
HttpServiceClient client = new HttpServiceClient(serviceURL);
DBServiceRequest request = new DBServiceRequest();
request.setSqlStatement(statement);
DBServiceResponse response = new DBServiceResponse(client.executeRequest(request));
其中服务的URL是这样的:
http://myHost/servlet/httpservice.HttpService.
细节
上面我们已经看到了框架结构中的所有元素,现在来看看那些有趣的细节。首先让我们来注意一下协议层。我们应该如何创建包装XML负载的HTTP POST 请求?我们应该如何处理HTTP响应?
HTTP 请求是标准化的、基于ASCII的、与一个Web 服务器的socket通讯。这里有一个例子:
POST /servlet/
httpService.Httpservice HTTP/1.0
Host: localhostt:80
Content-Type: text/xml
Content-Length: 248
<?xml version='1.0' encoding='utf-8' ?>
<http-request>
<requestType>DBService</requestType>
<request>
<sql-statement>
SELECT * FROM MyTable
</sql-statement >
</request>
</http-request>
来自Web服务器的响应如下所示:
HTTP/1.0 200 OK
Date: Fri, 24 Nov 2000 16:09:57 GMT
Status: 200
Servlet-Engine: Tomcat Web Server/3.1 (JSP 1.1;
Servlet 2.2; Java 1.3.0; Windows 2000 5.0 x86;
java.vendor=Sun Microsystems Inc.)
Content-Type: text/xml
Content-Length: 726
Content-Language: en
<?xml version='1.0' encoding='utf-8' ?>
<http-response>
<responseMessage>OK</responseCode>
<responseCode>200</responseCode>
<response>
<result-set>
</result-count>2 </result-count>
<row>
<col name='col1'>value11</col>
<col name='col2'>value12</col>
</row>
<row>
<col name='col1'>value21</col>
<col name='col2'>value22/</col>
</row>
<result-set>
</response>
</http-response>
下面的代码显示了HttpServiceClient类的执行,它将处理HTTP 请求的所有细节。你能看到,一旦你了解了那些请求的准确格式,这就是一个非常简单的过程:
public class HttpServiceClient
{
private static final String HTTP_VERSION = "1.0";
private static final String HTTP_POST_REQUEST ="POST";
private static final String HEADER_HOST = "Host";
private static final String HEADER_CONTENT_TYPE = "Content-Type";
private static final String XML_MIME_TYPE = "text/xml";
private static final String
HEADER_CONTENT_LENGTH = "Content-Length";
private static final int DEFAULT_PORT = 80;
private String serviceUrl;
private int returnCode;
private String returnMessage;
private Reader responsePayload;
public HttpServiceClient(String serviceUrl)
{
this.serviceUrl = serviceUrl;
}
public ServiceResponse executeRequest(ServiceRequest request)
throws HttpServiceException
{
try
{
String data = request.serializeRequestToString("utf-8");
postRequest(data);
//check for failures
if(returnCode != 200)
throw new HttpServiceException(returnMessage);
InputSource source =
new InputSource(responsePayload);
DOMParser parser = new DOMParser();
parser.parse(source);
ServiceResponse serviceResponse =
new ServiceResponse(parser.getDocument());
String theResponse =
serviceResponse.serializeResponseToString("utf-8");
System.err.println(theResponse);
return serviceResponse;
}
catch(Exception ex)
{
ex.printStackTrace(System.err);
throw new HttpServiceException(ex.getMessage());
}
}
private void postRequest(String payload)
throws HttpServiceException
{
PrintWriter out = null;
BufferedReader in = null;
URL url = null;
try
{
url = new URL(serviceUrl);
// No port? use default port 80
int port = url.getPort () < 0 ? DEFAULT_PORT : url.getPort();
Socket soket = new Socket (url.getHost (), port);
out = new PrintWriter (soket.getOutputStream ());
in = new BufferedReader (new InputStreamReader(soket.getInputStream ()));
}
catch (Exception ex)
{
throw new HttpServiceException ("error opening socket: " + ex.getMessage ());
}
out.print (HTTP_POST_REQUEST + " " + url.getFile() + "HTTP/" + HTTP_VERSION + "\r\n");
out.print (HEADER_HOST + ": " + url.getHost () + ':' + url.getPort () + "\r\n");
out.print (HEADER_CONTENT_TYPE + ": " + XML_MIME_TYPE + "\r\n");
out.print (HEADER_CONTENT_LENGTH + ": " + payload.length () + "\r\n");
out.print ("\r\n");
out.print (payload);
out.print ("\r\n\r\n");
out.flush ();
try
{
String statusLine = in.readLine();
System.err.println(statusLine);
parseStatusLine(statusLine);
}
catch (Exception ex)
{
throw new HttpServiceException ("error parsing HTTP status line: " + ex.getMessage ());
}
// I will ignore all the headers and keep reading
// until I get an empty line
try
{
String headerLine = null;
while ((headerLine = in.readLine ()) != null)
{
if (headerLine.length () == 0)
break;
}
}
catch (Exception ex)
{
throw new HttpServiceException ("error reading HTTP headers: " + ex.getMessage ());
}
//what remains of the input Stream is my payload
responsePayload = in;
}
private void parseStatusLine(String statusLine)
throws Exception
{
StringTokenizer st =
new StringTokenizer (statusLine);
// this is the HTTP Version
st.nextToken ();
returnCode = Integer.parseInt (st.nextToken ());
StringBuffer retMessage = new StringBuffer ();
while (st.hasMoreTokens ())
{
retMessage.append (st.nextToken ());
if (st.hasMoreTokens ())
{
retMessage.append (" ");
}
}
returnMessage = retMessage.toString ();
}
}
Web服务器接受了HTTP请求后,它就创建一个HttpService servlet的新例示,接着调用doPost()方法,在HttpServletRequest和HttpServletResponse对象中传递。然后这个servlet 就恢复XML负载,并且创建ServiceRequest类的一个例示,最后将其转交给正确的处理器:
Document dom;
DOMParser parser = new DOMParser();
InputSource input = new InputSource(request.getInputStream());
parser.parse(input);
dom = parser.getDocument();
ServiceRequest serviceRequest = new ServiceRequest(dom);
String className = PACKAGE_NAME + "." + request.getRequestType() + "Handler";
HttpServiceHandler handler = Class.fromName(className).newInstance();
handler.handleRequest(request);
下面的代码显示了DBServiceHandler类的执行情况,这个类创建数据库连接、执行查询并且生成DBServiceResponse对象。同样,要注意这个过程非常简单,因为许多复杂的问题都隐藏在ServiceResponse和ServiceRequest类及子类的后面了:
public class DBServiceHandler extends
HttpServiceHandler
{
public ServiceResponse processRequest(ServiceRequest req)
throws HttpServiceException
{
DBServiceRequest request = new DBServiceRequest(req);
String sql = request.getSqlStatement();
DBServiceResponse response = new DBServiceResponse();
Connection connection;
try
{
Class.forName("oracle.jdbc.driver.OracleDriver");
String connectionString = "jdbc:oracle:thin:@fender.openport.com:1521:iplp";
connection = DriverManager.getConnection(connectionString ,"op1","op1");
}
catch(ClassNotFoundException ex)
{
ex.printStackTrace(System.err);
response.setResponseCode(400);
response.setResponseMessage("Oracle driver not found");
return response;
}
catch(SQLException ex2)
{
ex2.printStackTrace(System.err);
response.setResponseCode(400);
response.setResponseMessage("Could Not Connect To Database!");
return response;
}
String theSql = sql.trim().toUpperCase();
ResultSet resultSet;
try
{
Statement statement =
connection.createStatement();
if(theSql.startsWith("SELECT"))
{
resultSet = statement.executeQuery(theSql);
Vector theResults = parseResultSet(resultSet);
response.setResultsCount(theResults.size() - 1);
response.setResultSet(theResults);
}
else
{
statement.executeUpdate(theSql);
response.setResultsCount(-1);
response.setResultSet(new Vector());
}
}catch(SQLException ex)
{
response.setResponseCode(400);
response.setResponseMessage(ex.getMessage());
return response;
}
response.setResponseCode(200);
response.setRe
[1] [2] 下一页