- 使用 WebHDFS REST API URL 访问各种文件系统接口。WebHDFS 提供了一种简单的标准方式从外部客户端执行 hadoop 文件系统操作。WebHDFS 的需求是,客户端需要通过预定义的端口、域名和相对上下文路径来直接连接名称节点和数据节点。文件读/写调用被重定向到相应的数据节点。它使用 Hadoop 集群的全部带宽来传输数据。WebHDFS 公开了支持 HDFS 的完整文件系统接口的 REST API。在该 REST API 中,在路径中插入了前缀
/webhdfs/v1
并在末尾附加了一个查询。因此,相应的 HTTP URL 拥有以下格式:http://<HOST>:<HTTP_PORT>/webhdfs/v1/<PATH>?op=...
请参见参考资料了解有关 WebHDFS API 的更多细节。
- 我们在清单 2 中已经看到,HttpfsUrl 的 VCAP_Services 环境变量是
http://50.97.161.83:14000/webhdfs/v1/
。要在包含此示例的视频文件的 HDFS 服务器文件系统上执行各种操作,可以根据您想要执行的任务来调用这些 URL:- 教程目录列表:
http://50.97.161.83:14000/webhdfs/v1/tmp/test/tutorial/ mathCourses?user.name=root&op=LISTSTATUS
清单 3. REST JSON 输出
{"FileStatuses":{"FileStatus":[ { "accessTime":0,"blockSize":0,"group":"supergroup","length":0, "modificationTime":1396734651621,"owner":"hdfs","pathSuffix": "basicMaths","permission":"755","replication":0,"type":"DIRECTORY" }, { "accessTime":0,"blockSize":0,"group":"supergroup","length":0, "modificationTime":1396734691621,"owner":"hdfs","pathSuffix": "everydayMaths","permission":"755","replication":0,"type":"DIRECTORY" } ]}}
- 获取教程目录的状态:
http://50.97.161.83:14000/webhdfs/v1/tmp/test/tutorial?user.name=root&op=GETFILESTATUS
清单 4. REST JSON 输出
{"FileStatus": { "accessTime":0,"blockSize":0,"group":"supergroup","length": 0,"modificationTime":1396734603190,"owner":"hdfs","pathSuffix": "","permission":"755","replication":0,"type":"DIRECTORY" } }
- 从一个特定的位置 (tutorial/../session-1) 打开视频文件 (demo.mp4):
http:// 50.97.161.83:14000/webhdfs/v1/tmp/test/tutorial/mathCourse/basicMaths/addition/sessions/elementary/session-1/demo.mp4?op=OPEN&user.name=root&namenoderpcaddress=0.0.0.0:8020&offset=0
清单 5. REST JSON 输出
[{ "isSuccessful": true, "responseTime": 10042, "statusCode": 200, "statusReason": "OK", "text": <binary data of demo.mp4 file> “responseHeaders": { "Content-Length": "1393457", "Content-Type": "application\/octet-stream"} }]
- 教程目录列表:
接下来,是时候集成 IBM Worklight 与 IBM InfoSphere BigInsights 了。
MyTutorial 应用程序是一个使用 IBM Worklight Studio 创建的混合应用程序。MyProject 项目被创建为混合项目。在项目内部,有一个名为 HadoopIntegrationAdapter 的 HTTP 适配器,用于调用 HDFS REST API。这个适配器使用了 REST URL http://supal.vm.cloudera.com:14000/webhdfs/v1/tmp/test/<directory name>?user.name=root&op=LISTSTATUS。
图 14 显示了 HTTP 适配器的 XML 配置文件,包含服务器域名、端口和应用程序要调用的过程。
图 14. Worklight Studio 中的 MyProject 混合项目
清单 6 中所示的函数是该适配器的核心调用函数,它使用了 Worklight WL.Server.invokeHttp 客户端 API 调用 WebHDFS REST URL 来列出某个给定目录的内容。MyTutorial 应用程序登录页面有一个选项,允许用户提供该目录来获取一个课程目录清单。
清单 6. 过程 listHDFSFileStatus()
function listHDFSFileStatus(dirName) { path = getPath(dirName); var input = { method : 'get', returnedContentType : 'json', path : path, parameters : { "user.name" : "root", "op" : "LISTSTATUS" }, }; var response = WL.Server.invokeHttp(input); return response; } function getPath(dirName) { return 'webhdfs/v1/tmp/test/' + dirName; }
清单 7 给出了创建一个 JSON 结构的函数实现,该结构包含目录内容以及所有子目录和文件。
清单 7. 过程 treeDirectoryContents(rootDir)
var mTreeResponseArray = { "directoryItems" : [], "statusCode": 0, "statusReason": "", }; var mCanonicalPathNTypeArray = []; var mlevelCount=1; function treeDirecotryContents(rootDir) { WL.Logger.info ("SUPAL-->treeDirecotryContents invoked with parameter : '" + rootDir + "'"); var response = listHDFSFileStatus(rootDir); var childCount = response.FileStatuses.FileStatus.length; for (var i = 0; i < childCount; i++) { var fileDir = response.FileStatuses.FileStatus[i].pathSuffix; var fileType = response.FileStatuses.FileStatus[i].type; var canonicalPath = rootDir + "/" + fileDir; mlevelCount =rootDir.split("/").length; var dirItem = {}; var dirLevel = "level-"+mlevelCount; var parentLevel = (mlevelCount-1) == 0? null : "level-"+(mlevelCount-1); if (fileType == 'DIRECTORY') { dirItem[dirLevel] = { "id" : dirLevel, "type" : "DIRECTORY", "path" : canonicalPath, "parentId" : parentLevel, "label" : rootDir, "numberOfChild":childCount}; mCanonicalPathNTypeArray.push(dirItem); treeDirecotryContents(canonicalPath); } else { dirItem[dirLevel] = { "id" : dirLevel, "type" : "FILE", "path" : canonicalPath, "parentId" : parentLevel, "label" : rootDir, "numberOfChild":childCount}; mCanonicalPathNTypeArray.push(dirItem); } } mTreeResponseArray.directoryItems = mCanonicalPathNTypeArray; //Set HttpAdapter response code mTreeResponseArray.statusCode=response.statusCode; mTreeResponseArray.statusReason= response.statusReason; return mTreeResponseArray; }
此函数在内部调用了 listHDFSFileStatus() 方法,它是该适配器最初的调用方法。对于对该子目录的每次递归调用,该操作都会处理适配器的 JSON 响应,随后将它返回给定根目录名称(例如 tutorial)的另一种 JSON 格式的目录项,如清单 8 所示。
清单 8. 给定目录的 JSON 格式的目录项
{ "directoryItems": [ { "level-1": { "id": "level-1", "label": "tutorial", "numberOfChild": 1.0, "parentId": null, "path": "tutorial\/mathCourses", "type": "DIRECTORY" } }, { "level-2": { "id": "level-2", "label": "tutorial\/mathCourses", "numberOfChild": 2.0, "parentId": "level-1", "path": "tutorial\/mathCourses\/basicMaths", "type": "DIRECTORY" } }, { "level-3": { "id": "level-3", "label": "tutorial\/mathCourses\/basicMaths", "numberOfChild": 4.0, "parentId": "level-2", "path": "tutorial\/mathCourses\/basicMaths\/addition", "type": "DIRECTORY" } }, . . . . . . . . . . . . . . . . . . . . . . . . . { "level-3": { "id": "level-3", "label": "tutorial\/mathCourses\/everydayMaths", "numberOfChild": 5.0, "parentId": "level-2", "path": "tutorial\/mathCourses\/everydayMaths\/tableFormulas", "type": "DIRECTORY" } } ], "isSuccessful": true, "statusCode": 200, "statusReason": "OK" }
这种 JSON 格式使得使用者(也即应用程序的 main.js)能够轻松地创建目录浏览器来查看应用程序登录 Web 页面。
MyTutorial 应用程序在 HadoopIntegrationAdapter HTTP 适配器上调用了上面的 treeDirectoryContents 过程,而请求是通过 HTTP Server 转到 Worklight HTTP 适配器。清单 9 给出了 main.js 中调用 HadoopIntegrationAdapter 的代码。
清单 9. 调用 Hadoop Integration HTTP 适配器的应用程序
function createHDFSExplorer() { alert("In createHDFSExplorer"); var div = $("#hdfsExplorerArea"); // clear the screen div.html(''); div.append("Please Wait..!! \n"); var invocationData = { adapter : 'HadoopIntigrationAdapter', procedure : 'treeDirecotryContents', parameters : [ $('#rootDirName').val() ] }; WL.Client.invokeProcedure(invocationData, { onSuccess : handleSuccess, onFailure : handleFailure, }); }// createHDFSExplorer function handleSuccess(result) { var httpStatusCode = result.status; var div = $("#hdfsExplorerArea"); div.append("done"); if (200 == httpStatusCode) { var isSuccessful = result.invocationResult.isSuccessful; if (true == isSuccessful) { var dirItems = result.invocationResult.directoryItems; var htmlStr = createHTMLDirContent(dirItems); // clear the screen div.html(''); // add dir explorer div.append("<p>" + htmlStr + "</p>"); } else { div.append("In handleSuccess-->1 Request Failed!"); } } else { div.append("In handleSuccess-->2 Request Failed!"); } }// handleSuccess
在 createHDFSExplorer 函数中,invocationData 变量使用根目录的名称作为参数,以便浏览包含课程视频文件的 HDFS 文件系统。这个根目录名称将由应用程序的用户提供。
从适配器获得成功响应后,应用程序会调用 JavaScript 函数 createHTMLDirContent() 来创建 HTML5 字符串。该 HTML5 代码表示在 MyTutorial 应用程序登录页面上呈现的 HDFS 文件系统目录结构。清单 10 中给出了实现此用途的代码段。
清单 10. 在获得成功响应后调用这个 JS 函数来创建 HTML 目录内容
点击查看代码清单
关闭 [x]
清单 10. 在获得成功响应后调用这个 JS 函数来创建 HTML 目录内容
var mHtmlStrTxt = '<h1 class="small" > MyTutorial Hadoop HDFS file explorer for showing and playing video demo. </h1>'; function createHTMLDirContent(dirItems) { var htmlStrTxt = mHtmlStrTxt; htmlStrTxt += '<ul>'; var iLen = dirItems.length; var fileType = ""; for (var i = 0; i < iLen; i++) { var dirObj = dirItems[i]; for ( var k in dirObj) { var dirDetails = dirObj[k]; fileType = dirDetails["type"]; var canonicalPath = dirDetails["path"]; var videoUrl = getRESTUrl(canonicalPath); if (fileType == "DIRECTORY") { htmlStrTxt += '<li>' + canonicalPath + '</li>'; } else { htmlStrTxt += '<li>' + canonicalPath + ' <input type="button" id="submitPlayVideo" value="play" onclick="openNativePageToPlayVideo(\'' + videoUrl + '\')"/> </li>'; } }// for dirObj }// for dirItems htmlStrTxt += '</ul>'; return htmlStrTxt; }// createHTMLDirContent /* Retrun RESTFul URL for video file **/ function getRESTUrl(fileName) { var url = "http://supal.vm.cloudera.com:50075/webhdfs/v1/tmp/test/"; url += fileName; url += "?op=OPEN&user.name=root&namenoderpcaddress=0.0.0.0:8020&offset=0"; return url; }
这个 JavaScript 函数创建了一个 RESTful URL 字符串(getRESTUrl 方法返回给定视频文件的一个动态创建的视频 URL),传输用户想要的视频需要使用这个字符串。
由于 MyTutorial 是一个混合应用程序,所以 Worklight 提供了一种简单而又直观的机制来将一个原生页面合并到应用程序中。Worklight 使应用程序能够在 Web 页面与原生页面之间导航,在页面之间传输数据。通过使用此 Worklight 功能,可以使用 HTML5 创建一个基于 Web 的登录页面来实现了这个 MyTutorial 混合应用程序,该应用程序能够打开使用 Android SDK 集创建的原生页面,用该页面来播放 .mp4 演示视频。在收到用户的请求时,应用程序可以返回到登录 Web 页面。
主要应用程序的 main.js JavaScript 文件包含一个名为 openNativePageToPlayVideo() 的函数(清单 11),它调用了原生页面来运行 Android Java API。
清单 11. 包含打开 Android 原生页面的函数的 main.js 文件
function openNativePageToPlayVideo(url) { var params = { urlParam : url }; // Open Android native page to play the video WL.NativePage.show('com.MyApplication.PlayMyVideo', returnFromNativePage, params); alert("In openNativePageToPlayVideo--->Exiting Native Page"); } /** * Invoked as a call-back on return from the Android native java page * * @param data */ function returnFromNativePage(data) { WL.Logger.debug("SUPAL--->Back from PlayMyVideo'"); }
被适配器的 onSuccess 处理函数调用时,该函数调用了 WL.NativePage.show 来打开 PlayMyVideo Java 文件,其中包含播放该视频的 Android 原生 API 代码。该函数包含一个参数,该参数包含 RESTful Web 服务 URL,可以使用此 URL 直接从 HDFS 服务器打开视频文件。
现在,是时候播放视频文件了。MyProject 在特定于 Android 的项目结构位置下包含一个 PlayMyVideo.java 类(图 15)。
图 15. 包含 Android 原生 Java 代码的项目文件夹
PlayMyVieo.java 被编码为一个 Android 活动的扩展。这个类使用了 Android VideoView 和 MediaController API(参见参考资料了解 API 细节)类在设备上播放和传输视频。VideoView 类用于提供在 Android 应用程序中播放来自远程来源的视频的各种选项。视频处于回放状态后,用户期望能够对它进行控制,而且通常能够实现此控制。MediaController 类通过公开熟悉的交互方法来处理此情形。
清单 12 给出了完整实现,包括显示和处理视频;启动、停止、播放、暂停和搜索视频,以及关闭原生视频窗口,将控制权返回给登录页面。
清单 12. PlayMyVideo.java 实现类
点击查看代码清单
关闭 [x]
清单 12. PlayMyVideo.java 实现类
public class PlayMyVideo extends Activity { String TAG = "com.MyApplication.PlayMyVideo"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String strUrl = getIntent().getStringExtra("urlParam"); setContentView(R.layout.video_view); try { //video_view is defined in layout.xml the area where video will be play in the native device screen. final VideoView videoView = (VideoView) findViewById(R.id.videoView1); Uri uri = Uri.parse(strUrl); MediaController mediaController = new MediaController(this); mediaController.setAnchorView(videoView); videoView.setMediaController(mediaController); videoView.setVideoURI(uri); videoView.requestFocus(); videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() { @Override public void onPrepared(MediaPlayer mp) { Log.i(TAG, "Duration = " + videoView.getDuration()); } }); videoView.start(); //Get Back to Web page button final Button closeButton = (Button)findViewById(R.id.button1); closeButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { finish(); } }); } catch (Throwable th) { Log.e(TAG, "SUPAL--->Error = " + th.toString()); } }//onCreate }//end of class
您可以看到,使用 Worklight 很容易实现一个解决方案,在 Android 设备上播放来自远程系统的视频文件。惟一面临的真正挑战是视频格式是否受 Android 支持。请参见参考资料,了解 Android 支持的媒体格式的更多细节。
回页首
实现流
MyApplication 登录页面是一个使用 HTML5 构建的基于表单的 Web 页面,包含一个输入文本框和一个按钮。输入文本用于指定特定的 mathCourses 目录,Explore 按钮用于执行浏览 HDFS 文件的事件。
onClick 按钮事件调用 createHDFSExplorer() 方法,该方法进而调用 HadoopIntergationAdapter 的 treeDirectoryContents 过程,以便使用底层 REST 协议与 Hadoop 生态系统进行集成。WL.Client.invokeProcedure 的 OnSuccess 响应处理函数将会适当处理收到的 JSON 响应后,然后创建一个 HTML 清单。
目录项的 HTML 清单包含一个播放视频文件的 HTML Play 按钮。Play 按钮的 onClick 操作代码是在运行时创建内容清单的时候使用视频 URL 动态创建的。
单击Play按钮会打开动态视频 URL 的原生页面,播放 Android 原生页面上的视频。
最后,Android 原生视频播放器中的Close按钮将用户返回到登录页面。
回页首
运行和测试结果
现在,是时候运行 MyTutorial 应用程序了。图 16 显示了 MyTutorial 登录页面。
图 16. MyTutorial 登录页面
用户在输入文本字段中提供根目录名称并单击explore。图 17 显示了 MyTutorial 页面,其中的 MyTutorial HDFS 目录显示了可供播放的视频。
图 17. MyTutorial HDFS 目录浏览器
点击查看大图
关闭 [x]
图 17. MyTutorial HDFS 目录浏览器
通过多种实现方法,可以让使用 JSON 定义的文件系统(包含视频文件)以移动友好的格式(比如一个可展开的树)显示在 HTML5 目录菜单中,不过,UI 自定义的示例不属于本文的讨论范围。
图 18 显示了直接从 Hadoop 系统传输 demo.mp4 视频的 Adnroid 原生页面。
图 18. 播放流视频
点击查看大图
关闭 [x]
图 18. 播放流视频
回页首
结束语
本文重点介绍了将 IBM InfoSphere BigInsights(它给企业带来了 Apache Hadoop)on IBM Bluemix 与 IBM Worklight 集成的概念。文中提供了一个在与云上的大数据存储库交互后,在移动设备上检索和传输视频文件的示例。希望这里提供的示例可以帮助您将这些想法应用到您自己 的移动开发项目中。
回页首
致谢
感谢以下人员的巨大贡献和对本文的重要审阅和建议:Radha Mohan De,授权顾问 IT 专家;Rajarshi Bhose,大数据分析常务顾问和 Rengia R Vasudevan,IBM 认证的高级 IT 架构师。