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 扩展组件概述
在开始开发之前,必须满足一下前提条件:
- 一个正常工作的 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
|
要集成 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 的网络库简单地使用我们的组件的基类。我们提供的新函数有:Init、Upload、IsList 和 FileSize。
清单 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返回一个布尔类型的值,表示对Read和ReadSegments函数的调用是否阻塞了:对于我们的流来说,这通常会返回 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 行)和 GetPort(GsiftpHandler.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 进行更多修改来支持协议特有的函数,例如 IsList 或 FileSize。在确保协议可以由标准网络组件或其他对 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 行中的 CPPSRCS 和 CSRCS 部分列出了所有的源文件。第 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 的完整编译指南(请参看 参考资料).
除了 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 的目录。在这个目录中,我们又创建了两个目录:chrome 和 components。最后,我们又在 chrome 目录中创建了 content 和 locale 目录。用来扩展 GUI 的文件应该被移动到 content 目录中。它们包括 overlay.js 和 overlay.xul。
一旦这些文件全部就绪之后,我们就需要将层叠使用的 DTD 文件放到 locale 目录中了。Locales 用来提供对各种语言的支持。在 locale 目录中,我们新建了一个 en-US 目录,并将 overlay.dtd 文件拷贝到这里。接下来,content 和 locale 目录都被压缩成一个 JAR 文件。要实现这种功能,我们可以转到 chrome 目录中并执行下面的命令: jar -cMf topaz.jar content locale。content 和 locale 目录现在可以删除了,因为它们都已经在 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.js 和 nsIGridLogin.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 风格的开源许可证。
学习
- 您可以参阅本文在 developerWorks 全球网站上的 英文原文。
-
学习更多有关 GridFTP 协议和工具的内容。
-
从源代码中了解有关
下载和编译 Mozilla Firefox 的详细指南。
-
确保阅读 developerWorks 上“构建统一的网格”系列文章。
-
学习更多有关 Grid Account Management Architecture (GAMA) 的内容。
-
了解有关 Mozilla XPCOM 接口 的详细内容。
-
学习有关 在扩展中使用依赖库的内容。
-
了解更多有关 配置 Mozilla 编译环境 的内容。
-
不要错过 Mozilla.org 上有关 XUL overlays 的内容。
-
请浏览
技术书架 上有关这些和其他技术主题的书籍。
-
请访问 developerWorks 中国网站 Grid computing 专区 中丰富的信息。
获得产品和技术
-
下载并安装 Topaz 扩展。
-
现在并安装 Globus Toolkit。
讨论
-
参与 developerWorks 网格论坛。

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

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


