IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Grid computing  >

将 GridFTP 集成到 Firefox 中

将网格协议构建到基于 Mozilla 的工具中

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 中级

Karan Bhatia (karan@sdsc.edu), 资深研究员, San Diego Supercomputer Center
Michela Taufer (mtaufer@utep.edu), 副教授, University of Texas at El Paso
Brent Stearn (flujul@sdsc.edu), 程序分析员, San Diego Supercomputer Center
Richard Zamudio (rzamudio@utep.edu), 研究生, University of Texas at El Paso
Daniel Catarino (dcatarino1@utep.edu), 程序员, University of Texas at El Paso

2006 年 10 月 23 日

本文将探索如何根据 Mozilla 框架在基于桌面的胖客户机应用程序中集成基于服务器的网格计算技术。作为这种方法的一个例子,我们将把 GridFTP 协议(这是作为 Globus Toolkit 的一部分开发的)集成到 Firefox Web 浏览器中,并将这些功能封装成 Firefox 的一个扩展。从而使用户可以使用与用户目前访问 FTP 文件相同的方式来访问在远程 GridFTP 服务器上存储的文件,而不需要安装和配置 Globus Toolkit 或显式地管理网格安全证书。通过这种方法可轻松扩展其他网格协议和 API 进行扩展。

GridFTP 与使用 Firefox 的桌面进行集成

GridFTP 协议是标准文件传输协议(FTP)的一个扩展,它可以支持基于 Globus GSI(Grid Security Infrastructure)的安全机制,使用条带化和并行机制而进行的高性能数据传输机制,并且可以支持不同 GridFTP 服务器之间进行的第三方传输。GridFTP 是 Globus Toolkit 的一个标准组件,包括服务器组件和一组客户机应用程序。

对 GridFTP 服务器的访问需要使用 GSI 进行用户身份验证,然后就可以使用客户机应用程序了,例如命令行应用程序 UberFTP。由于这个原因,GridFTP 用户必须在客户机机器上安装并配置 Globus Toolkit 软件 —— 这是一个很大的负担,增加了软件的复杂性。

相反,标准 FTP 则直接构建到了大部分浏览器中,这允许用户可以在浏览器中的地址栏中简单地输入一个 FTP URL(ftp://server:port/path),然后就可以浏览、上载或下载文件了。

在本文中,我们将展示如何将 GridFTP 协议集成到 Firefox 浏览器中,从而实现与标准 FTP 相同的行为。用户可以简单地提供一个 gsiftp URL(gsiftp://server:port/path),然后就可以浏览、上载或下载服务器上的文件了。用户身份验证是由 GAMA (Grid Account Management Architecture)系统提供的。这个扩展称为 Topaz,可以以二进制或源代码的形式使用。Topaz 目前可以支持 Linux® 和 MacOS X 系统。

一旦前提条件满足之后,我们就可以开始通过封装 Globus GridFTP 客户机协议实现到 Mozilla 组件框架中来构建 Firefox 扩展了。我们然后可以创建一个支持 gsiftp 机制的 协议处理器通道。伺候,我们将展示如何将新组件注册到 Mozilla 运行时中,以及如何对这个扩展进行编译。我们增加了一个简单的 可扩展用户界面语言(XUL)层,它为 Firefox 菜单增加了一个文件上载菜单,可以支持文件的上载。最后,我们将整个扩展封装到一个标准的跨平台的可安装(XPI)文件。


图 1. Firefox 扩展组件概述
Firefox 扩展组件概述





回页首


前提条件

在开始开发之前,必须满足一下前提条件:

  • 一个正常工作的 GridFTP 服务器,我们要访问这上面的文件(请参看 参考资料 中有关安装服务器组件的介绍)。
  • 运行 Linux 或 MacOS X 的节点。
  • Mozilla 框架的源代码 (请参看 参考资料)。下载并编译最新的 Firefox V1.5 发行版(目前是 V1.5.0.7)。为了简单性起见,我们假设这都在一个名为 /mozilla_src 的目录中。
  • Globus Toolkit 发行版提供的 Globus GridFTP 库(请参看清单 1)。我们将这些库文件放到 /Globus 中,并将 GLOBUS_LOCATION 环境变量设置为这个位置。

清单 1. Globus GridFTP 库
 
      > export GLOBUS_LOCATION=/Globus
      > ls $GLOBUS_LOCATION
      libltdl_gcc64dbg.so.3 
      libcrypto_gcc64dbg.so.0 
      libglobus_common_gcc64dbg.so.0 
      libssl_gcc64dbg.so.0 
      libglobus_callout_gcc64dbg.so.0 
      libglobus_proxy_ssl_gcc64dbg.so.0 
      libglobus_openssl_error_gcc64dbg.so.0 
      libglobus_openssl_gcc64dbg.so.0 
      libglobus_gsi_cert_utils_gcc64dbg.so.0 
      libglobus_gsi_sysconfig_gcc64dbg.so.0 
      libglobus_oldgaa_gcc64dbg.so.0 
      libglobus_gsi_callback_gcc64dbg.so.0 
      libglobus_gsi_credential_gcc64dbg.so.0 
      libglobus_gsi_proxy_core_gcc64dbg.so.0 
      libglobus_gssAPI_gsi_gcc64dbg.so.0 
      libglobus_gss_assist_gcc64dbg.so.0 
      libgssAPI_error_gcc64dbg.so.0 
      libglobus_xio_gcc64dbg.so.0 
      libglobus_io_gcc64dbg.so.0 
      libglobus_ftp_control_gcc64dbg.so.0 
      libglobus_ftp_client_gcc64dbg.so.0





回页首


将 Globus 库作为一个 XPCOM 组件进行集成

要集成 GridFTP 协议,第一个步骤是开发与 GridFTP 服务器进行交互的 GridFTP 客户机端协议实现。我们不用开发自己的实现,而是使用 Globus Toolkit 提供的实现,即在上一节中我们安装的 Globus 库。

为了利用 Mozilla 代码库中 GridFTP 协议的实现,我们将这个库封装成一个 Mozilla XPCOM 组件。XPCOM 是 Mozilla 核心使用的一个类似于 CORBA 的组件模型,也是开发人员通过最大限度的灵活性而实现特性的扩展机制。XPCOM 组件都具有平台和语言无关的接口描述语言(IDL)接口。这些接口描述了组件提供了哪些功能,是一个组件使用另外一个组件所需要的惟一信息。这种机制允许我们的组件使用现有库,并且也可以被现有库使用,同时还可以在 JavaScript 中使用。我们的组件 GsiftpStream 将使用 Globus 库来通过 GridFTP 上载和下载数据。

与所有的 XPCOM 组件一样,我们的 GsiftpStream 从一个描述自己提供的功能的接口入手。 IGsiftpStream.idl(请参看清单 2)是在我们的源代码目录 /mozilla_src/mozilla/extensions/gsiftp/ 中创建的,它为了声明对另外两个 Mozilla 接口的支持而定义了 4 个新函数,这两个接口是: nsISupports,这个接口是所有的 XPCOM 组件都需要的;nsIInputStream,它通常用作允许 Mozilla 的网络库简单地使用我们的组件的基类。我们提供的新函数有:InitUploadIsListFileSize


清单 2. IGsiftpStream.idl 文件

 1	#include "nsIURI.idl"
 2	#include "nsISupports.idl"
 3	#include "nsIInputStream.idl"
 4	
 5	[scriptable, uuid(771d14db-4592-4604-873f-e21c7582b847)]
 6	interface IGsiftpStream: nsIInputStream
 7	{
 8	  void Init(in nsIURI url,in PRInt32 port);
 9	  void Upload(in string url, in string file);
10  	  boolean IsList();
11  	  PRUint64 FileSize();
12	};

接下来,一个称为 XPIDL 的工具被用来为 GsiftpStream 创建头文件和模板 C++ 实现。在头文件 GsiftpStream.h 中,我们在第 10 到 12 行包括了 Globus 函数的定义,在第 31 到 33 行使用了宏来声明为 IGsiftpStream.idl 接口所定义的 4 个函数(有关 XPCOM 接口的更多信息请参考 参考资料)。


清单 3. GsiftpStream.h 文件

1	#ifndef GsiftpStream_h__
2	#define GsiftpStream_h__
3
4	#define _POSIX_C_SOURCE 200112L
5	#include <stdio.h>
6	#include <iostream>
7	#include <string>
8 	#include "nsIURI.h"
9	#include "nsCOMPtr.h"
10	#include "globus_common.h"
11	#include "ftpSession.h"
12	#include "globus_ftp_client.h"
13	#include "IGsiftpStream.h"
14	#define GSIFTP_PORT 2811
15	#define GSIFTP_BUFFER_SIZE (4*1024)
16	#define GSIFTPSTREAM_CLASSNAME "GsiftpStream"
17	#define GSIFTPSTREAM_CONTRACTID "@mozilla.org/extensions/gsiftp/gsiftp-stream;1"
18	#define GSIFTPSTREAM_CID                             \
19	  {                                                  \
20	0xd4d0ee8f,                                        \
21	0x8925,                                            \
22	0x4974,                                            \
23	{ 0x82, 0x1b, 0xfd, 0xb8, 0x33, 0x82, 0x58, 0x47 } \
24	}
25
26	class GsiftpStream: public IGsiftpStream
27	{
28	public:
29	GsiftpStream();
30	virtual ~GsiftpStream();
31	NS_DECL_IGSIFTPSTREAM
32	NS_DECL_ISUPPORTS
33	NS_DECL_NSIINPUTSTREAM
34	PRInt32                             gsPort;
35	nsCOMPtr<nsIURI>                    gsURL;
36	};
37	#endif /* GsiftpStream_h__ */

尽管我们已经指出在接口中实现了 nsISupports,但是我们实际上并不需要自己编写任何代码。XPIDL 创建了在 IGsiftpStream.idl 编译过程中所有需要的宏。

然而,我们的确需要实现 nsIInputStream 的 5 个函数。这些函数期望在所有的流中都存在,可以由其他组件以一种统一的方法来使用:

  • Available 返回流中可用字节数。流的大小保存在 dataAvailable 变量中,它是在流创建时设置的。
  • Close 被调用来关闭流和到 Globus 的连接。
  • IsNonblocking 返回一个布尔类型的值,表示对 ReadReadSegments 函数的调用是否阻塞了:对于我们的流来说,这通常会返回 true。
  • Read 将指定数目的字节放到一个给定缓冲区中。在这个函数中,我们首先将流缓冲区的内容拷贝到给定位置处,然后检查是否有更多数据需要获取。
  • ReadSegments 允许 XPCOM 组件访问流的缓冲区。这个函数并没有使用,如果它被调用了就会返回一个错误。

GsiftpStream.cpp 中,Init 函数实现了建立到 GridFTP 服务器连接所需要的工作。

首先,我们创建了一个 nsIGridLogin 实例来检查我们是否具有访问服务器所需要的正确证书( nsIGridLogin 的详细内容已经超出了本文的范围,不过简要来说,nsIGridLogin 提供了对用户进行身份验证和获取用户的代理证书所需要的功能,Globus 库会使用它来对用户在服务器上进行身份验证和授权)。

一旦我们具有正确的证书之后,就可以从所传递的 URL 对象中获取主机和路径信息,并构建一个 URL 字符串来传递给 Globus。接下来,我们获取正在访问的文件的大小,并建立一个数据连接来下载文件。如果 URL 没有返回文件数据,我们就可以监视这个路径是一个目录,并重新建立一个控制连接来显示目录的内容。数据传输的内容是使用上面介绍的 Read 函数完成的。

Upload 用来发起文件到指定 URL 的传输。为了使用我们的协议来发送内容,我们使用希望上载文件的服务器位置的 URL 来调用 Upload 函数。我们建立了一个到服务器的连接,并像上面的 Init 函数一样进行认证。然后,我们建立并显示一个文件选择对话框来允许用户指定要上载的文件。然后打开所选择的文件,建立数据连接,并将这个文件传输到服务器上。

IsList 返回在 Init 中设置的 isListResult 的值,它表示正在现在的数据是文件还是目录列表。

FileSize 返回正在下载的文件的大小。

一旦流组件完成之后,所有与 Globus Toolkit 功能的交互都是通过它进行处理的,具体来说就是通过在 IGsiFTPStream IDL 文件中定义的接口进行的。下一个步骤是将这个组件关联到处理 Firefox 代码的协议中。下一节将介绍这个过程。





回页首


创建协议处理器和通道

为了让 Firefox(或其他基于 Mozilla 的应用程序)理解何时将对数据的传输控制权委托给协议进行,我们必须还哟阿创建协议处理器 nsIProtocolHandler 和一个通道 nsIChannel 的实现。协议处理器用来在 URL 处理的初始阶段使用适当的协议实现来匹配某种模式 —— 协议名类似于 gsiftp://http://ftp://

在 gsiftp 文件夹中,我们创建了 GsiftpHandler.h。协议处理器的头文件大部分都是相同的。惟一区别在于使用的端口号和惟一联系 ID。对于 GsiftpHandler.cpp 来说,我们将 GetScheme 方法(GsiftpHandler.cpp 中的第 18 到 23 行)和 GetPortGsiftpHandler.cpp 中的第 25 到 30 行)分别修改成返回 gsiftp 所使用的模式和端口号。在 NewURI 函数中,我们创建了一个标准 URL 对象的实例,并使用 URL 字符串和 gsiftp 端口号对其进行了初始化(GsiftpHandler.cpp 中的第 54 行)。在 AllowPort 函数中,我们允许对缺省的 gsiftp 控制连接端口和数据端口进行访问(GsiftpHandler.cpp 中的第 60 到 67 行)。


清单 4. GsiftpHandler.cpp 文件

 1	NS_IMPL_THREADSAFE_ISUPPORTS4(GsiftpHandler,
 2	                              nsIProtocolHandler,
 3	                              nsIProxiedProtocolHandler,
 4	                              nsIObserver,
 5	                              nsISupportsWeakReference)
 6	
 7	NS_METHOD
 8	GsiftpHandler::Create(nsISupports* aOuter, const nsIID& aIID, void* *aResult){
 9	    GsiftpHandler* ph = new GsiftpHandler();
10	    if (ph == nsnull)
11         return NS_ERROR_OUT_OF_MEMORY;
12	    NS_ADDREF(ph);
13	    nsresult rv = ph->QueryInterface(aIID, aResult);
14	    NS_RELEASE(ph);
15	    return rv;
16	}
17    
18	NS_IMETHODIMP
19	GsiftpHandler::GetScheme(nsACString &result)
20	{
21	    result = NS_LITERAL_CSTRING("gsiftp");
22	    return NS_OK;
23	}
24
25	NS_IMETHODIMP
26	GsiftpHandler::GetDefaultPort(PRInt32 *result)
27	{
28	    *result = GSIFTP_PORT; 
29	    return NS_OK;
30	}
31
32	NS_IMETHODIMP
33	GsiftpHandler::NewURI(const nsACString &aSpec,
34	                             const char *aCharset,
35	                             nsIURI *aBaseURI,
36	                             nsIURI **result)
37	{
38	 nsC AutoString spec(aSpec);
39	 char *fwdPtr = spec.BeginWriting();
40
41	 // now unescape it... %xx reduced inline to resulting character
42	 PRInt32 len = NS_UnescapeURL(fwdPtr);
43
44	 // NS_UnescapeURL() modified spec's buffer, truncate to ensure
45	 // spec knows its new length.
46	 spec.Truncate(len);
47
48       // return an error if we find a NUL, CR, or LF in the path
49       if (spec.FindCharInSet(CRLF) >= 0 || spec.FindChar('\0') >= 0)
50	    return NS_ERROR_MALFORMED_URI;
51       nsresult rv;
52       nsCOMPtr<nsIStandardURL> url = do_CreateInstance(kStandardURLCID, &rv);
53       if (NS_FAILED(rv)) return rv;
54         rv=url->Init(nsIStandardURL::URLTYPE_\
                   STANDARD,GSIFTP_PORT,aSpec,aCharset, aBaseURI);
55	    if (NS_FAILED(rv)) return rv;
56	    return CallQueryInterface(url, result);
57	}
58
60	NS_IMETHODIMP 
61	GsiftpHandler::AllowPort(PRInt32 port, const char *scheme, PRBool *retval)
62		{
63		    PRInt32 dataPort = 2119;
64		    *retval = ( (port == GSIFTP_PORT) || (port == dataPort) );
65		    return NS_OK;
66		}
67	}
GsiftpHandler.cpp

通道是用来打开、关闭连接和从连接中读取数据所使用的流之上的一个抽象且与协议无关的层次。通过实现 nsIChannel 接口,我们不需要对 Mozilla 进行更多修改来支持协议特有的函数,例如 IsListFileSize。在确保协议可以由标准网络组件或其他对 GridFTP 敏感的组件使用的同时,这个通道可以传递任务负载。

对于 GsiftpChannel.h 来说,我们为 GsiftpStream 增加了其他 #includes 语句,并声明了这个对象,例如这个通道以后使用 URL。

GsiftpChannel.cpp 中(清单 5),我们包括了 Globus 库使用的头文件(此处没有给出)。在 Init 函数中,我们从协议使用的 URL 对象中提取出 GridFTP 服务器主机名和服务器上的数据路径(第 45 到 68 行)。这个 URL 对象是由协议处理器从用户在位置栏中提供的 URL 中创建的。

AsyncOpen 函数中,我们创建了一个流对象实例(第 86 到 90 行),并使用 URL 对象和端口号对其进行了初始化(第 92 行),并使用它创建了一个传输服务(第 95 到 109 行)。在第 111 到 115 行中,我们创建了一个名为 InputStreamPump 的对象来反复从流中读取数据,并将反馈数据发给通道。在 OnStartRequest 函数中,我们检查是否正在下载目录列表(第 145 行)。如果是这种情况,我们就创建一个流转换器(第 146 行 到 159 行)将 FTP 目录列表修改成 HTTP 索引格式,这样 Firefox 就可以将其以 Web 页面的形式呈现出来。


清单 5. GsiftHandler.cpp 文件

...
...
...
29	static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID);
30	static NS_DEFINE_CID(kStreamConverterServiceCID, NS_STREAMCONVERTERSERVICE_CID);
31	#define FILE_INPUT_STREAM_CID \
32	{ 0xe356a20, 0xc7ec, 0x11d3, \
33	{ 0x8c, 0xda, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3 } }
34	#define BUFFER_SEG_SIZE (5*1024)
35	#define BUFFER_MAX_SIZE (64*1024)
36	
37	nsresult
38	GsiftpChannel::Init(nsIURI* uri, nsIProxyInfo* \
        proxyInfo, nsICacheSession* session)
39	{
40	    nsresult rv = NS_OK;
41	    nsCAutoString autoBuffer;
42	    NS_ASSERTION(uri, "no uri");
43	
44	    // setup channel state
45	    mURL = uri;
46	    mProxyInfo = proxyInfo;
47	    mPort = GSIFTP_PORT;
48	    rv = mURL->GetPath(mDir); 
49	    if (NS_FAILED(rv)){
50		NS_LITERAL_STRING("Failed to obtain a valid path.").get());
51		return rv;
52	    }
53	    rv = mURL->GetHost(mHost);
54	    if (NS_FAILED(rv)){
55		NS_LITERAL_STRING("Failed to obtain a valid host.").get());
56		return rv;
57	    }
58	    if (mDir.IsEmpty())
59	    {
60	        mDir.Assign("/");
61	    }
62	    if (mHost.IsEmpty())
63	        return NS_ERROR_MALFORMED_URI;
64	    mContentType = NS_LITERAL_CSTRING("text/html");
65	    if (!mLock) {
66	        mLock = PR_NewLock();
67	        if (!mLock) return NS_ERROR_OUT_OF_MEMORY;
68	    }
69	
70	    mIOService = do_GetIOService(&rv);
71	    if (NS_FAILED(rv)) return rv;
72	    mCacheSession = session;
73	    return NS_OK;
74	}
75	
76	NS_IMETHODIMP
77	GsiftpChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *ctxt)
78	{
79	   nsresult rv = NS_OK;
80	   rv = NS_CheckPortSafety(mPort, "gsiftp");
81	   if (NS_FAILED(rv)) return rv;
82	   nsCOMPtr<nsIEventQueue> eventQ;
83	   rv = NS_GetCurrentEventQ(getter_AddRefs(eventQ));
84	   if (NS_FAILED(rv)) return rv;
85	
86	   mStream = do_CreateInstance\
               ("@mozilla.org/extensions/gsiftp/gsiftp-stream;1", &rv);
87	   if (NS_FAILED(rv)){ 
88			NS_LITERAL_STRING("Failed to create a stream.").get());
89			return rv;
90		 }
91	
92	  rv = mStream->Init(mURL, mPort); 
93	
94	  nsCOMPtr<nsIInputStream> inStream = do_QueryInterface(mStream);
95	  nsCOMPtr<nsIStreamTransportService> sts =\
              do_GetService(kStreamTransportServiceCID, &rv);
96	  if (NS_FAILED(rv)) return rv;
97	  rv = sts->CreateInputTransport(inStream, PRUint64(-1), \
          PRUint64(-1), PR_TRUE, getter_AddRefs(mTransport));
98	  if (NS_FAILED(rv)){
99			return rv;
100		 }
101	  rv = mTransport->SetEventSink(this, eventQ);
102	  if (NS_FAILED(rv)){
103			return rv;
104		 }
105	
106	  rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(inStream));
107	  if (NS_FAILED(rv)){
108			return rv;
109		 }
110	
111	  rv = NS_NewInputStreamPump(getter_AddRefs(mPump), inStream);
112	  if (NS_FAILED(rv)){
113	            NS_LITERAL_STRING("Failed to create a new \
                            input stream pump.").get());
114			return rv;
115		 }
116	
117	  rv = mPump->AsyncRead(this, nsnull);
118	  if (NS_FAILED(rv)) return rv;
119	  if (mLoadGroup)
120	    mLoadGroup->AddRequest(this, nsnull);
121	  mListener = aListener;
122	  mListenerContext = ctxt;
123	  return NS_OK;
124	}
125	
126	NS_IMETHODIMP
127	GsiftpChannel::OnStartRequest(nsIRequest *request, nsISupports *aContext) 
128	{
129       PR_LOG(gGsiftpLog, 
130	           PR_LOG_DEBUG, 
131	           ("GsiftpChannel::OnStartRequest() called [this=%x, request=%x]\n", 
132	            this, request));
133	  if (NS_SUCCEEDED(mStatus))
134	        request->GetStatus(&mStatus);
135	  //setting the content size using the information from the stream
136	  mStream->FileSize(&progressMax);
137	  SetContentLength(progressMax);
138	  nsresult rv = NS_OK;
139	  if (mListener) {
140	      
141	      /*check if it is a list*/        
142	      PRBool listAnswer;
143	      mStream->IsList(&listAnswer);
144	
145	      if (listAnswer) {
146	        nsCOMPtr<nsIStreamConverterService> serv =
147	        	 do_GetService(kStreamConverterServiceCID/*"\
                     @mozilla.org/streamConverters;1"*/, &rv);
148	        if (NS_SUCCEEDED(rv)) {
149	       	 	nsCOMPtr<nsIStreamListener> converter;
150	         	rv = serv->AsyncConvertData("text/ftp-dir",
151	                                            APPLICATION_HTTP_INDEX_FORMAT,
152	                                            mListener,
153	                                            mURL,
154	                                            getter_AddRefs(converter));
155	                if (NS_SUCCEEDED(rv)) {
156	                    mListener = converter;
157	                }
158	        }
159	      }
160	        rv = mListener->OnStartRequest(this, mListenerContext);
161	        if (NS_FAILED(rv)) return rv;
162	   }
163	   return rv;
164	}
GsiftpChannel.cpp

现在 GridFTP 的功能都使用 Mozilla 框架封装起来了,我们需要让 Mozilla 了解运行时的其他组件。这个过程就称为 注册





回页首


注册组件

每个 XPCOM 组件都必须与 nsIModule 的实现建立关联,这是为了在这个框架中进行注册。模块会根据需要进行加载,从而将所请求的联系 ID 转换成组件的实例。清单 6 给出了一个使用 JavaScript 创建 GsiftpStream 实例的例子。


清单 6. 使用 JavaScript 创建 GsiftpStream 实例的例子

      var stream = 
      Components.classes["@mozilla.org/extensions/gsiftp/gsiftp-stream;1"].
         createInstance(Components.interfaces.IGsiftpStream); 

指定联系 ID @mozilla.org/extensions/gsiftp/gsiftp-stream;1 可以获得有关组件的信息,而 IGsiftpStream.idl 则说明了我们希望使用创建一个使用这个特定接口的实例。这些信息对应于清单 7 中的 GsiftpModule.cpp。第 8 到 11 行定义了处理器的类名、组件 ID、联系 ID 和组件的构造器。第 14 到 17 行为 GsiftpSteam 组件定义了相同的内容,第 21 行中的宏使用这些信息来实现这个模块所需要的其他函数。


清单 7. GsiftpModule.cpp 文件

 1	#include "nsIGenericFactory.h"
 2	#include "GsiftpHandler.h"
 3	#include "GsiftpStream.h"
 4
 5	NS_GENERIC_FACTORY_CONSTRUCTOR(GsiftpStream)
 6
 7	static const nsModuleComponentInfo gResComponents[] = {
 8	    { "The Gsiftp Protocol Handler", 
 9	      GSIFTPHANDLER_CID,
10	      "@mozilla.org/network/protocol;1?name=gsiftp",
11	      GsiftpHandler::Create 
12	    },
13	    {
14	      GSIFTPSTREAM_CLASSNAME,
15	      GSIFTPSTREAM_CID,
16	      GSIFTPSTREAM_CONTRACTID,
17	      GsiftpStreamConstructor, 
18	    }
19	};
20
21	NS_IMPL_NSGETMODULE("gsiftp", gResComponents)

这段代码完成之后,剩余的步骤包括编译扩展,增加 XUL 层,以及对扩展进行打包。





回页首


编译扩展

现在协议处理器、通道和流组件都已经完成了,我们需要修改 Mozilla 源代码的编译系统来增加对这个扩展的编译。

要作为这个框架的一部分进行编译,我们首先需要在 fsiftp 源目录中创建一个名为 Makefile.in 的 makefile 模板。在编译过程中,这会用来在编译目录中生成一个 makefile(请参看清单 8)。Makefile.in 的第 8 行将 Globus 库的目录加入到路径中。第 9 行说明我们使用了哪些库。下面出现的 Makefile.in 是 32 位版本的 Globus 库所使用的。对于 64 位的系统来说,这些库都是相同的,不过最后一个以 gcc64dbg 而不是 gcc32dbg 结尾。

第 20 到 32 行的 REQUIRES 部分列出了这个模块中使用的 XPCOM 组件。第 34 到 43 行中的 CPPSRCSCSRCS 部分列出了所有的源文件。第 45 到 47 行的 XPIDLSRCS 部分是我们的流使用的接口定义。


清单 8. Makefile.in 文件

1	DEPTH		= ../..
2	topsrcdir	= @top_srcdir@
3	srcdir		= @srcdir@
4	VPATH		= @srcdir@
5	
6	CFLAGS = -g  -Wall -I.  -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGE_FILES
7	
8	GLOBUS_LDFLAGS = -L$(GLOBUS_LOCATION)/lib 
9	GLOBUS_LIBS =  -lglobus_ftp_control_gcc32dbg -lglobus_io_gcc32dbg 
        -lglobus_xio_gcc32dbg -lgssAPI_error_gcc32dbg -lglobus_gss_assist_gcc32dbg 
        -lglobus_gssAPI_gsi_gcc32dbg -lglobus_gsi_proxy_core_gcc32dbg 
        -lglobus_gsi_credential_gcc32dbg -lglobus_gsi_callback_gcc32dbg 
        -lglobus_oldgaa_gcc32dbg -lglobus_gsi_sysconfig_gcc32dbg 
        -lglobus_gsi_cert_utils_gcc32dbg -lglobus_openssl_gcc32dbg 
        -lglobus_openssl_error_gcc32dbg -lglobus_callout_gcc32dbg 
        -lglobus_proxy_ssl_gcc32dbg -lglobus_common_gcc32dbg -lssl_gcc32dbg 
        -lcrypto_gcc32dbg -lltdl_gcc32dbg   -lm -ldl -ldl -lglobus_ftp_client_gcc32dbg 
10	
11	include $(DEPTH)/config/autoconf.mk
12	
13	MODULE		= gsiftp
14	LIBRARY_NAME	= gsiftp
15	EXPORT_LIBRARY	= 1
16	IS_COMPONENT	= 1
17	MODULE_NAME	= gsiftp
18	MOZILLA_INTERNAL_API = 1
19	
20	REQUIRES	= xpcom \
21			  string \
22	               widget \
23			  necko \
24			  mimetype \
25			  nspr \
26	               pref \
27	               intl \
28	               nkcache \
29	               embedcomponents \
30	               windowwatcher \
31	               dom \
32			  $(NULL)
33	
34	CPPSRCS	= \
35			GsiftpHandler.cpp \
36			GsiftpChannel.cpp \
37			GsiftpModule.cpp \
38			GsiftpStream.cpp \
39			$(NULL)
40	
41	CSRCS		= \
42			ftpSession.c \
43			$(NULL)
44	
45	XPIDLSRCS	= \
46			IGsiftpStream.idl \
47			$(NULL)
48	
49	EXTRA_DSO_LDOPTS = \
50			$(MOZ_COMPONENT_LIBS) \
51			$(GLOBUS_LDFLAGS) \
52			$(GLOBUS_LIBS) \
53			$(NULL)
54	
55	LIBS		= \
56			$(MOZ_COMPONENT_LIBS) \
57			$(GLOBUS_LDFLAGS) \
58			$(GLOBUS_LIBS) \
59			$(NULL)
60	
61	include $(topsrcdir)/config/rules.mk
62	
63	INCLUDES += -I@top_srcdir@/extensions/gsiftp
64	INCLUDES += -I$(GLOBUS_LOCATION)/include/gcc32dbg 
65	INCLUDES += -I$(srcdir)/../../dist/include/necko

在 Macs 系统上,我们需要将修改 Makefile.in 中库的列表(清单 14 中的第 9 行),并添加一个新行来强制编译共享库。


清单 9. Mac 系统上 Makefile.in 的变化

...
...
...
9	GLOBUS_LIBS = lglobus_common_gcc32dbg -lm -lglobus_ftp_client_gcc32dbg 
                      -lglobus_ftp_control_gcc32dbg
...
...
...
19	FORCE_SHARED = 1

为了让我们的代码在编译时也一起可以编译出来,我们必须要告诉 Mozilla 在哪里可以找到我们的 Makefile.in 文件。我们回到 Mozilla 源代码的顶层目录 mozilla。文件 allmakefiles.sh 中在编译过程中使用的所有 makefile 的列表。在这个文件中,我们搜索字符串 MOZ_EXTENSIONS,它后面是编译扩展所使用的 makefile 列表。我们增加如清单 10 所示的命令。


清单 10. allmakefiles.sh 中的变化

        gsiftp ) MAKEFILES_extensions="$MAKEFILES_extensions
            extensions/gsiftp/Makefile
            " ;;

这些行说明 gsiftp 将控制我们的源代码文件(现在一起作为一个扩展标识)是否进行编译。要启用编译新扩展选项,我们需要将 gsiftp 添加到 Mozilla 的 .mozconfig 配置文件内部启用的扩展列表中(清单 11 的第 12 行)。清单 11 给出了一个 Linux® 系统上使用的 .mozconfig 例子。有关 Firefox 编译选项配置的更详细信息,请参看 Mozilla 的编译配置(请参看 参考资料)。


清单 11. .mozconfig 文件

1	. $topsrcdir/browser/config/mozconfig
2
3	# Options for client.mk.
4	mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-@CONFIG_GUESS@
5
6	# Options for 'configure' (same as command-line options).
7	ac_add_options --enable-extensions=default,gsiftp
8	ac_add_options --disable-static
9	ac_add_options --enable-shared
10	ac_add_options --disable-optimize
11	ac_add_options --disable-debug
12	ac_add_options --disable-tests

对于 Macs 系统来说,我们还需要将如清单 12 所示的选项添加到 .mozconfig 文件中。


清单 12. Mac 系统上 .mozconfig 文件的变化
 
      ac_add_options --with-macos-sdk=/Developer/SDKs/MacOSX10.2.8.sdk
      ac_add_options --enable-default-toolkit=mac

要编译 Firefox,我们需要在 Mozilla 源代码的顶层目录中运行下面的命令: % make -f client.mk build 。我们也可以参考 Firefox 的完整编译指南(请参看 参考资料).





回页首


XUL 层

除了 XPCOM 组件之外,我们还需要为用户添加指定上载文件的能力。为了简单性起见,我们添加了一个 Upload 菜单项,在查看 gsiftp 位置时,它会出现在 Firefox 的 File 菜单中。基于 Mozilla 的应用程序的用户界面都是以 XUL 编写的,可以通过使用 XUL 层来利用新的 GUI 元素进行扩充。

尽管清单 13 中给出的层次很少,但是它却实现了 3 件重要的事情:为上载描述了一个新的 XUL menuItem(第 9 到 10 行),告诉 Firefox 将新的 menuItem 添加到什么地方(第 8 行),指定包含在用户点击菜单项时所执行的函数的新 JavaScript 文件(第 6 行)。所包括的 CSS 和 DTD 仅仅是为了确保我们可以修改在 menuItem 中显示的内容,而不需要每次都修改 XUL。


清单 13. Overlay.xul 文件

 1	<?xml version="1.0"?>
 2	<?xml-stylesheet href="chrome://topaz/skin/overlay.css" type="text/css"?>
 3	<!DOCTYPE overlay SYSTEM "chrome://topaz/locale/overlay.dtd">
 4	<overlay id="upload-overlay"
 5	xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> 
 6	<script src="overlay.js"/>
 7
 8	<menupopup id="menu_FilePopup">
 9      <menuitem id="upload-hello" label="&upload;" 
10	                 oncommand="Upload.onMenuItemCommand(event);"/> 
11	</menupopup>
12	</overlay>

当我们选择 menuItem 时,就会从最近活动的 Firefox 窗口中获得 URL 作为目标目录,创建流组件的一个实例,将其传递给 URL,并调用它的 Upload() 函数。





回页首


打包

在将新的扩展分发给其他人时,必须首先进行打包。对诸如 Firefox 这种基于 Mozilla 的扩展都可以封装成 XPI,这是一个压缩文件,其中包含一个扩展的 XPCOM 组件、URL、JavaScript 和应用程序安装它所需要的元数据。第一个步骤是创建存储扩展所需要的一系列目录。

首先,我们创建了一个名为 topaz 的目录。在这个目录中,我们又创建了两个目录:chromecomponents。最后,我们又在 chrome 目录中创建了 contentlocale 目录。用来扩展 GUI 的文件应该被移动到 content 目录中。它们包括 overlay.jsoverlay.xul

一旦这些文件全部就绪之后,我们就需要将层叠使用的 DTD 文件放到 locale 目录中了。Locales 用来提供对各种语言的支持。在 locale 目录中,我们新建了一个 en-US 目录,并将 overlay.dtd 文件拷贝到这里。接下来,contentlocale 目录都被压缩成一个 JAR 文件。要实现这种功能,我们可以转到 chrome 目录中并执行下面的命令: jar -cMf topaz.jar content localecontentlocale 目录现在可以删除了,因为它们都已经在 JAR 文件中了。

现在我们在 chrome 目录中新建一个 chrome.manifest 文件,并告诉 Firefox 到哪里去定位扩展包中的 XUL 和 JavaScript 文件(请参看清单 14)。chrome.manifest 还告诉 Firefox 在其主 browser.xul 上覆盖我们的新 XUL 文件。


清单 14. chrome.manifest 文件

      content topaz jar:chrome/topaz.jar!/content/
      overlay chrome://browser/content/browser.xul chrome://topaz/content/overlay.xul
      locale topaz en-US jar:chrome/topaz.jar!/locale/en-US/

接下来我们要添加由 Mozilla 编译系统所创建的扩展库。它们都在 mozilla_src/mozilla/build_directory/extensions/gsiftp 目录中,其中 build_directory 是由 .mozconfig 文件中 MOZ_OBJDIR 开关指定的目录。在 mozilla_src/mozilla/build_directory/extension/gsiftp 中,我们应该可以找到一个名为 _xpidlgen 的文件夹。我们将编译后的 IDL 文件 gsiftp.xpt 从这里拷贝到组件目录中。同样,我们又将 nsGridLogin.jsnsIGridLogin.xpt 从它们创建的地方拷贝到 components 目录中。

由于我们希望自己的扩展可以正常工作,而不需要在本地机器上安装完整的 Globus,因此我们还需要对 Globus 的共享库进行打包。Linux 和 Mac 在添加第三方 Globus 库时使用了不同的方法。

对于 Linux 来说,我们创建一个 stub 组件,它可以帮助我们将 Globus 库加载到内存中,这样它们就可以在需要时对扩展进行访问了。如果没有这个 stub 组件,GsiftpStream 可能就不会注册,这是因为 Firefox 不需要寻找其他 Globus 依赖性。在 stub 组件加载 Globus 库之后,其余扩展也可以加载,并在请求 gsiftp URL 时准备好使用了。有关 stub 组件的更多信息可供参考(请参看 参考资料)。

接下来,我们在与 components 目录相同的一级目录中创建了 libraries 目录,并将清单 1 所给出的所有共享 Globus 库拷贝到这个目录中。我们还将 libgsiftp.so 共享库拷贝到 libraries 目录中,并将 stub 库移动到 components 目录中。

要在 Mac 系统上将第三方库添加到扩展中,我们需要使用下面的技术。在将 libgsiftp.dylib 拷贝到 components 目录中之后,我们就创建一个名为 libs 的子目录,并将必须的 Globus 库拷贝到这里(这些库与 Linux 系统上的库基本上是相同的,除了其扩展名是 .dylib,而不是 .so)。

在拷贝库之后,我们需要使用 otool(OS X 上等效于 ldd 的工具)来看一下每个库的依赖性:otool -L filename。举例来说,让我们考虑清单 15 中的 libglobus_common_gcc32dbg.0.dylib


清单 15. Otool 用法

      otool -L libglobus_common_gcc32dbg.0.dylib 
  
      libglobus_common_gcc32dbg.0.dylib:
      /Users/Globus/Globus/lib/libglobus_common_gcc32dbg.0.dylib
      /Users/Globus/Globus/lib/libltdl_gcc32dbg.3.dylib
      /usr/lib/libSystem.B.dylib

正如我们可以看到的一样,libglobus_common_gcc32dbg.0.dylib 有两个依赖性:另外一个 Globus 库和一个标准的系统库。通常,依赖性会显示这些库所在的完整路径。如果我们试图对这些库进行封装,并在另外一台计算机上使用这些库,那么动态加载程序(dyld)就会无法解析这些依赖性。解决方案是使用 install_name_tool 工具将这些路径修改为安装它的相对路径(@loader_path/),如清单 16 所示。


清单 16. install_name_tool -change

 install_name_tool -change \
         /Users/Globus/Desktop/globus_sdk/lib/libltdl_gcc32dbg.3.dylib \
         @loader_path/libltdl_gcc32dbg.3.dylib libglobus_common_gcc32dbg.0.dylib

我们需要对每个 Globus 库的所有依赖库都执行这个操作。注意对于系统库的依赖性不需要改变。我们使用一个不同标记(-id)执行 install_name_tool 来修改这个库的标识,这样它就可以正确地告知系统了。


清单 17. install_name_tool -id

       install_name_tool libglobus_common_gcc32dbg.0.dylib -id \
         libglobus_common_gcc32dbg.0.dylib

一个安装清单 install.rdf 中包含了 Firefox 的 Extension Manager 安装 XPI 所使用的元数据。这个文件应该放到 XPI 顶层目录中,并包含清单 18 中的信息。


清单 18. install.rdf 文件

1	    <?xml version="1.0"?>
2	    <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
3	         xmlns:em="http://www.mozilla.org/2004/em-rdf#">
4 
5	    <Description about="urn:mozilla:install-manifest">
6
7	    <em:id>{9905D264-65DE-43B7-9BFC-F77DAD2C3D94}</em:id>
8	    <em:name>Topaz (Firefox 1.5 edition)</em:name>
9	    <em:version>1.0 beta</em:version>
10	    <em:description>Gsiftp protocol handling interface</em:description>
11	    <em:creator>Richard Zamudio</em:creator>
12	    <em:type>2</em:type>
13	    <em:iconURL>chrome://topaz/content/topaz_small.gif</em:iconURL>
14	    <!-- optional items -->
15	    <em:contributor>Brent Stearn</em:contributor>
16	    <em:contributor>Karan Bhatia</em:contributor>
17	    <em:contributor>Michela Taufer</em:contributor>
18	    <em:contributor>Daniel Catarino</em:contributor>
19	    <em:homepageURL>http://gcl.utep.edu/projects/topaz</em:homepageURL>
20	    <!-- Firefox -->
21	    <em:targetApplication>
22	      <Description>
23	        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
24	        <em:minVersion>1.5</em:minVersion>
25	        <em:maxVersion>1.5.0.*</em:maxVersion>
26	      </Description>
27	    </em:targetApplication>
28
29	 <!-- Packages, Skins and Locales that this extension registers -->
30	 <em:file>
31	 <Description about="urn:mozilla:extension:file:topaz.jar">
32	 <em:package>content/</em:package>
33	 <em:locale>locale/en-US/&</em:locale>
34	 </Description>
35	 </em:file>
36	 </Description>
37
38	 </RDF>

注意: 在清单 18 的第 7 行中,有一个 ID 域。这个 ID 是这个扩展的统一惟一标识符(UUID)。在编写这个扩展时,我们需要为扩展创建一个惟一 ID。

最后一个步骤,我们需要将 topaz 目录使用 zip 压缩成一个包。要实现这种功能,我们需要在 topaz 目录中执行下面的命令:% zip -r topaz.xpi * .

另外有一种方法可以用来编译并打包扩展,我们也可以使用这种方法。但是我们目前并没有使用这种方法,这是因为它需要执行几个额外步骤来包括 Globus 库。

最后,我们安装扩展,以便确保一切都正常工作。在将这个文件放到一个配置可以识别 XPI mime 类型的服务器(或 localhost)上之后,安装就变得非常简单了:我们之需要将其位置告知浏览器即可。一旦我们重新启动 Firefox 之后,就应该可以简单地在浏览器的地址栏中输入 gsiftp:/// URL 开始使用 gsiftp 扩展了。





回页首


结束语

本文介绍了将网格协议集成到 Mozilla Firefox 浏览器中的一种方法。这种技术可以有效地对基于服务器的网格进行扩展,使起包括用户桌面或笔记本上的受控资源。具体来说,此处介绍的技术为用户提供了一种安全、集成的方法使用 gsiftp URL 模式来访问 GridFTP 文件。以后的版本将可以支持更高级的选项,例如第三方传输和并行流处理。

概括起来,本文展示了:

  • 如何将 Globus Toolkit 客户机封装成一个 XPCOM 组件。
  • 如何编写协议处理器和通道与 Globus 进行交互。
  • 如何修改 Mozilla 的编译过程来包括新组件和 Globus Toolkit 客户机库。
  • 如何将前面创建的内容打包成一个基于 XPI 的 Firefox 扩展。

Topaz 扩展可以从 Topaz Web 站点上下载,源代码也能下载到。代码使用了 BSD 风格的开源许可证。



参考资料

学习

获得产品和技术

讨论


作者简介

Karan Bhatia

Karan Bhatia 负责领导 San Diego Supercomputer Center 的 Grid Middleware Development 小组的工作,同时还是很多规模大小不同的计算机项目的研究员。他的研究兴趣包括面向服务的 SOA 和广域分布式计算。


Michela Taufer

Michela Taufer 负责领导 University of Texas, El Paso 的 Global Computing Lab 的工作。她的兴趣包括计算化学、物理学和生物学中资源密集型和时间密集型的应用程序所使用的新算法和架构;将大规模仿真程序迁移到基于公共资源的全球计算系统上;在异构分布式系统上的大规模仿真的性能分析、建模和优化。


Brent Stearn

Brent Stearn 是 Gemstone 项目的首席程序员,这是在 San Diego Supercomputer Center 的 Grids and Clusters 分部进行的一个由 NSF 赞助的项目。


Richard Zamudio 是位于 El Paso 的德州大学的 Global Computing Lab 的一名研究生。


Daniel Catarino

Daniel Catarino 正在位于 El Paso 的德州大学的计算机科学系攻读自己的大学学位。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?




回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款